Load libraries
library(here)
library(readr)
library(tidyr)
library(dplyr)
library(lubridate)
library(ggplot2)
library(stringr)
library(purrr)
library(readxl)
Read the data
db_resurv<-read_tsv(here("data", "edited", "db_resurv.csv"))
Aviso: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
dat <- vroom(...)
problems(dat)Rows: 425310 Columns: 69── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (31): Country, Biblioreference, Nr. table in publ., Cover abundance scal...
dbl (22): PlotObservationID, PlotID, TV2 relevé number, Nr. relevé in table,...
lgl (16): Lon1, Lon2, Lon3, Lon4, Lat1, Lat2, Lat3, Lat4, X1, X2, X3, X4, Y1...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Problems (do not affect us)
problems<-problems(db_resurv)
sort(unique(problems$col))
[1] 7 13
names(db_resurv[c(7,13)])
[1] "Nr. relevé in table" "Altitude"
No problems really!
Update coordinates
Create new column with old coordinates if new not available, and with
new if available.
db_resurv <- db_resurv %>%
mutate(Lon_updated = ifelse(is.na(Lon_prec),Longitude,Lon_prec),
Lat_updated = ifelse(is.na(Lat_prec),Latitude,Lat_prec))
print(db_resurv, width = Inf)
ISSUE 1: ReSurvey plot is NA
Careful! Sometimes ReSurvey plot is NA.
nrow(db_resurv%>%filter(is.na(RS_CODE)))
[1] 0
nrow(db_resurv%>%filter(is.na(`ReSurvey site`)))
[1] 0
nrow(db_resurv%>%filter(is.na(`ReSurvey plot`)))
[1] 111
write_csv(db_resurv %>% filter(is.na(`ReSurvey plot`)),
here("output", "csv","issue1.csv"))
ISSUE 2: Coordinates are NA
Coordinates are also NA in some cases.
nrow(db_resurv%>%filter(is.na(Lon_updated) & is.na(Lat_updated )))
[1] 949
write_csv(db_resurv %>% filter(is.na(Lon_updated) & is.na(Lat_updated)),
here("output", "csv","issue2.csv"))
But Resurvey plot and coordinates are never missing at the same
time.
nrow(db_resurv%>%
filter(is.na(`ReSurvey plot`) & is.na(Lon_updated) & is.na(Lat_updated)))
[1] 0
ISSUE 3: Different ReSurvey observations within the same plot have
different coordinates
Careful! In some cases different ReSurvey observations within the
same plot have different coordinates.
Create two new columns in db_resurv: coordinates_equal indicating if
coordinates are exactly equal between ReSurvey observations, and
coordinates_consistent, indicating if coordinates are consistent between
ReSurvey observations (consistent meaning that difference < 0.001
degrees).
# Define a threshold (e.g., 0.001 degrees for longitude/latitude differences)
threshold <- 0.001
db_resurv <- db_resurv %>%
group_by(RS_CODE, `ReSurvey site`, `ReSurvey plot`) %>%
mutate(
lon_range = ifelse(all(is.na(Lon_updated)), NA,
max(Lon_updated, na.rm = T) -
min(Lon_updated, na.rm = T)),
lat_range = ifelse(all(is.na(Lat_updated)), NA,
max(Lat_updated, na.rm = T) -
min(Lat_updated, na.rm = T)),
coordinates_equal = ifelse(is.na(Lon_updated) & is.na(Lat_updated), NA,
lon_range == 0 & lat_range == 0),
coordinates_consistent = ifelse(is.na(Lon_updated) & is.na(Lat_updated), NA,
lon_range < threshold &
lat_range < threshold)
) %>%
ungroup() %>%
select(-lon_range, -lat_range)
write_csv(db_resurv %>% filter(coordinates_equal==FALSE),
here("output", "csv","issue3.csv"))
db_resurv %>%
group_by(RS_CODE,`ReSurvey site`, `ReSurvey plot`) %>%
summarize(is_equal = all(coordinates_equal),
is_consistent = all(coordinates_consistent),
.groups = "drop") %>%
mutate(coordinate_status = case_when(
is_equal ~ "Equal",
!is_equal & is_consistent ~ "Consistent (< 0.001º)",
!is_equal & !is_consistent ~ "Inconsistent (> 0.001º)")) %>%
count(coordinate_status)%>%
mutate(percentage = n / sum(n) * 100) %>%
ggplot(aes(x = percentage, y = coordinate_status, fill = coordinate_status)) +
geom_bar(stat = "identity") +
geom_text(aes(label = paste0(round(percentage, 1), "%")),
position = position_stack(vjust = 0.5), size = 3) +
labs(x = "Percentage of Plots", y = NULL) +
theme(axis.text.y = element_text(size = 12)) +
coord_flip() + theme(legend.position = "none")
ggsave(filename=here("output", "figures","issue3.tiff"),
width=10,height=7,units="cm",dpi=300)

ISSUE 4: Some plots have only one resurvey
When ReSurvey plot is not NA, use the unique combination of RS_CODE,
ReSurvey site and ReSurvey plot to uniquely define each ReSurvey plot
(as defined in metadata). When ReSurvey plot is NA (111 rows), use the
unique combination of RS_CODE, ReSurvey site and updated coordinates to
uniquely define each ReSurvey plot. Check how many resurveys
(i.e. different years are there for each unique combination).
count_resurveys <- db_resurv %>%
# Convert dates to date format and get the year
mutate(date = dmy(`Date of recording`), year = year(date)) %>%
group_by(RS_CODE, `ReSurvey site`,
# If ReSurvey plot is not NA,
# group by RS_CODE, `ReSurvey site`, `ReSurvey plot`
`ReSurvey plot` = ifelse(is.na(`ReSurvey plot`),
NA_character_, `ReSurvey plot`),
# If ReSurvey plot is NA, group by coordinates
Lon_updated = ifelse(is.na(`ReSurvey plot`), Lon_updated, NA_real_),
Lat_updated = ifelse(is.na(`ReSurvey plot`) , Lat_updated, NA_real_)
) %>%
summarise(
# Get how many different years for each unique group
distinct_years=n_distinct(year),
# Get how many different dates for each unique group
distinct_dates=n_distinct(date), .groups = "drop")
Summary stats:
summary(count_resurveys$distinct_years)
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.000 2.000 2.000 3.449 4.000 55.000
sd(count_resurveys$distinct_years)
[1] 2.677649
Histograms:
# For all data
ggplot(count_resurveys, aes(x = distinct_years)) +
geom_histogram(fill = "white", color = "black", bins = 55)+
xlab("Number of ReSurvey observations (different years)") +
ylab("Number of plots")
ggsave(filename=here("output", "figures","issue4.tiff"),
width=11,height=7,units="cm",dpi=300)

Number and proportion of plots with only 1 resurvey (should not be
so!)
nrow(count_resurveys%>%filter(distinct_years==1))
[1] 1158
nrow(count_resurveys%>%filter(distinct_years==1))/nrow(count_resurveys)
[1] 0.009573571
write_csv(count_resurveys%>%filter(distinct_years==1),
here("output", "csv","issue4.csv"))
ISSUE 5: Datasets with only presence/absence
db_resurv %>%
filter(`Cover abundance scale`=="Presence/Absence") %>%
distinct(Dataset)
ggplot(db_resurv %>%
mutate(pres_or_ab =ifelse(`Cover abundance scale`=="Presence/Absence",
"Presence/Absence", "Abundance"),
DK_Naturdata_Res = ifelse(Dataset == "DK_Naturdata_Res",
"Y", "N")),
aes(pres_or_ab, fill = DK_Naturdata_Res)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage", x = NULL)
ggsave(filename=here("output", "figures","issue5.tiff"),
width=12,height=7,units="cm",dpi=300)

For DK_Naturdata_Res - info about habitat from Jerker’s file (see
below).
ISSUE 6: Observations with wrong country (GIS)
Read text file wrong_countries obtained in ArcGIS:
wrong_countries <- read_delim(here("data", "clean","wrong_countries.txt"),
delim = ";")
Rows: 1850 Columns: 19── Column specification ────────────────────────────────────────────────────────
Delimiter: ";"
chr (12): Country, RS_CODE, ReSurvey_s, ReSurvey_p, Lon_update, Lat_update, ...
dbl (7): FID, Join_Count, TARGET_FID, plot_uniqu, year, obs_unique, PlotObsID
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
write_csv(wrong_countries, here("output", "csv","issue6.csv"))
ISSUE 7: Different cover abundance scales
ggplot(db_resurv, aes(`Cover abundance scale`)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations", x = "Cover abundance scale") +
coord_flip()
ggsave(filename=here("output", "figures","issue7.tiff"),
width=18,height=10,units="cm",dpi=300)

ISSUE 8: Wrong EUNIS codes
Used this info in metadata file:
Expert system classification to EUNIS habitats (https://zenodo.org/records/4812736 ; https://floraveg.eu/habitat/). I am sending you legend
for EUNIS classification version 2022-10-16 with all codes and meanings,
directly prepared from expert system file (second sheet) - it is
slightly different from published version in ZENODO (https://zenodo.org/records/4812736 , little bit old
dated now) and from https://floraveg.eu/habitat/ (little bit newer than in
current EVA version).
Qa = mires and Qb = wetlands P units – in floraveg.eu there is
slightly different classification (https://floraveg.eu/habitat/overview/P), but in EVA is
still this classification of P:
P Surface waters Pa Base-poor spring and spring brook Pb Calcareous
spring and spring brook Pc Brackish-water vegetation Pd Fresh-water
small pleustophyte vegetation Pe Fresh-water large pleustophyte
vegetation Pf Fresh-water submerged vegetation Pg Fresh-water nymphaeid
vegetation Ph Oligotrophic-water vegetation Pi Dystrophic-water
vegetation Pj Stonewort vegetation
Presence of “!” simply means that for one unit there are two or more
different formulas, e.g. R11 and R11!. So it is only technical
stuff.
Multiple assignment of relevé – no priority, alphabetical order,
e.g. N16!,S66,S81 means that relevé can be assigned to all 3 units: N16
Mediterranean and Macaronesian coastal dune grassland (grey dune), S66
Mediterranean halo-nitrophilous scrub and S81 Canarian xerophytic
scrub
No value present in Expert System – relevé didn´t enter expert system
classification (= it means that some prerequisites are missing)
“~” – relevé entered expert classification however was not classified
to any EUNIS unit +
Clean info on Expert system column and separate it when there are
several codes.
db_resurv <- db_resurv %>%
mutate(
# Clean 'Expert System' column by removing "!" and replacing "~" with NA
`Expert System` = case_when(
`Expert System` == "~" ~ NA_character_, # Replace "~" with NA
TRUE ~ str_replace_all(`Expert System`, "!", "") # Remove "!"
)
) %>%
# Separate the values in 'Expert System' into multiple columns
separate(
`Expert System`,
into = c("EUNISa", "EUNISb", "EUNISc", "EUNISd"),
sep = ",",
extra = "drop", # Drop extra values if there are more than columns
fill = "right", # Fill missing values with NA for cases with fewer values
remove = FALSE # Keep the original 'Expert System' column
)
Calculate how many different EUNIS codes have been assigned:
db_resurv <- db_resurv %>%
mutate(
# Count the number of non-NA values across the EUNIS columns
n_EUNIS = rowSums(!is.na(select(., starts_with("EUNIS"))))
)
ggplot(db_resurv, aes(n_EUNIS)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Number of differnt EUNIS codes assigned") + coord_flip()

ggplot(db_resurv %>% filter(n_EUNIS > 0), aes(n_EUNIS)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Number of differnt EUNIS codes assigned") + coord_flip()

Correct some EUNIS codes that are probably wrong:
db_resurv <- db_resurv %>%
mutate(across(starts_with("EUNIS"), ~ case_when(
. == "N16M" ~ "N16",
. == "Sa" ~ "V4",
. == "Sb" ~ "V5",
. == "T1CT" ~ "T1C",
. == "N15A" ~ "N15",
TRUE ~ .
)))
Add columns for the different EUNIS levels:
db_resurv <- db_resurv %>%
mutate(
# EUNISa levels
EUNISa_1 = substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 2, 1)),
EUNISa_2 = ifelse(
nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 3, 2),
substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 3, 2)),
NA_character_
),
EUNISa_3 = ifelse(
nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 4, 3),
substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 4, 3)),
NA_character_
),
EUNISa_4 = ifelse(
nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 5, 4),
substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 5, 4)),
NA_character_
),
# EUNISb levels
EUNISb_1 = substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 2, 1)),
EUNISb_2 = ifelse(
nchar(EUNISb) >= ifelse(str_starts(EUNISb, "MA"), 3, 2),
substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 3, 2)),
NA_character_
),
EUNISb_3 = ifelse(
nchar(EUNISb) >= ifelse(str_starts(EUNISb, "MA"), 4, 3),
substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 4, 3)),
NA_character_
),
EUNISb_4 = ifelse(
nchar(EUNISb) >= ifelse(str_starts(EUNISb, "MA"), 5, 4),
substr(EUNISb, 1, ifelse(str_starts(EUNISb, "MA"), 5, 4)),
NA_character_
),
# EUNISc levels
EUNISc_1 = substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 2, 1)),
EUNISc_2 = ifelse(
nchar(EUNISc) >= ifelse(str_starts(EUNISc, "MA"), 3, 2),
substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 3, 2)),
NA_character_
),
EUNISc_3 = ifelse(
nchar(EUNISc) >= ifelse(str_starts(EUNISc, "MA"), 4, 3),
substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 4, 3)),
NA_character_
),
EUNISc_4 = ifelse(
nchar(EUNISc) >= ifelse(str_starts(EUNISc, "MA"), 5, 4),
substr(EUNISc, 1, ifelse(str_starts(EUNISc, "MA"), 5, 4)),
NA_character_
),
# EUNISd levels
EUNISd_1 = substr(EUNISd, 1, ifelse(str_starts(EUNISc, "MA"), 2, 1)),
EUNISd_2 = ifelse(
nchar(EUNISd) >= ifelse(str_starts(EUNISd, "MA"), 3, 2),
substr(EUNISd, 1, ifelse(str_starts(EUNISd, "MA"), 3, 2)),
NA_character_
),
EUNISd_3 = ifelse(
nchar(EUNISd) >= ifelse(str_starts(EUNISd, "MA"), 4, 3),
substr(EUNISd, 1, ifelse(str_starts(EUNISd, "MA"), 4, 3)),
NA_character_
),
EUNISd_4 = ifelse(
nchar(EUNISd) >= ifelse(str_starts(EUNISd, "MA"), 5, 4),
substr(EUNISd, 1, ifelse(str_starts(EUNISd, "MA"), 5, 4)),
NA_character_
)
)
Create new columns with descriptions for the level 1 codes:
db_resurv <- db_resurv %>%
mutate(
EUNISa_1_descr = case_when(
EUNISa_1 == "V" ~ "Vegetated man-made habitats",
EUNISa_1 == "U" ~ "Inland habitats with no or little soil",
EUNISa_1 == "T" ~ "Forests and other wooded land",
EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISa_1 == "R" ~ "Grasslands",
EUNISa_1 == "Q" ~ "Wetlands",
EUNISa_1 == "P" ~ "Inland waters",
EUNISa_1 == "N" ~ "Coastal habitats",
EUNISa_1 == "MA" ~ "Marine habitats",
TRUE ~ NA_character_
),
EUNISb_1_descr = case_when(
EUNISb_1 == "V" ~ "Vegetated man-made habitats",
EUNISb_1 == "U" ~ "Inland habitats with no or little soil",
EUNISb_1 == "T" ~ "Forests and other wooded land",
EUNISb_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISb_1 == "R" ~ "Grasslands",
EUNISb_1 == "Q" ~ "Wetlands",
EUNISb_1 == "P" ~ "Inland waters",
EUNISb_1 == "N" ~ "Coastal habitats",
EUNISb_1 == "MA" ~ "Marine habitats",
TRUE ~ NA_character_
),
EUNISc_1_descr = case_when(
EUNISc_1 == "V" ~ "Vegetated man-made habitats",
EUNISc_1 == "U" ~ "Inland habitats with no or little soil",
EUNISc_1 == "T" ~ "Forests and other wooded land",
EUNISc_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISc_1 == "R" ~ "Grasslands",
EUNISc_1 == "Q" ~ "Wetlands",
EUNISc_1 == "P" ~ "Inland waters",
EUNISc_1 == "N" ~ "Coastal habitats",
EUNISc_1 == "MA" ~ "Marine habitats",
TRUE ~ NA_character_
),
EUNISd_1_descr = case_when(
EUNISd_1 == "V" ~ "Vegetated man-made habitats",
EUNISd_1 == "U" ~ "Inland habitats with no or little soil",
EUNISd_1 == "T" ~ "Forests and other wooded land",
EUNISd_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISd_1 == "R" ~ "Grasslands",
EUNISd_1 == "Q" ~ "Wetlands",
EUNISd_1 == "P" ~ "Inland waters",
EUNISd_1 == "N" ~ "Coastal habitats",
EUNISd_1 == "MA" ~ "Marine habitats",
TRUE ~ NA_character_
)
)
Plot for EUNISa_1 (the first assigned EUNIS in cases of multiple
assignations, level 1):
ggplot(db_resurv, aes(EUNISa_1_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 1") + coord_flip()

ggplot(db_resurv %>% filter(!is.na(EUNISa_1_descr)), aes(EUNISa_1_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 1") + coord_flip()
ggsave(filename=here("output", "figures","issue8.tiff"),
width=18,height=10,units="cm",dpi=300)

ISSUE 9: Manipulated plots and info on manipulation type
ggplot(db_resurv, aes(`Manipulate (y/n)`)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Manipulation")
ggsave(filename=here("output", "figures","issue9.tiff"),
width=10,height=8,units="cm",dpi=300)

List of Type of Manipulation in manipulated plots (mixed
information):
write_csv(data.frame(unique(db_resurv$`Type of manipulation`)),
here("output", "csv","issue9.csv"))
ISSUE 10: Location method
ggplot(db_resurv, aes(`Location method`)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Location method") + coord_flip()
ggsave(filename=here("output", "figures","issue10.tiff"),
width=18,height=8,units="cm",dpi=300)

ISSUE 11: Resurvey project types
unique(db_resurv$RS_PROJTYP)
[1] "resampling" "permanent" "permanent (man)" "Permanent (man)"
[5] "Resampling"
Unify codes:
db_resurv <- db_resurv %>%
mutate(RS_PROJTYP = recode(RS_PROJTYP,
"Resampling" = "resampling",
"Permanent (man)" = "permanent (man)"))
unique(db_resurv$RS_PROJTYP)
[1] "resampling" "permanent" "permanent (man)"
ggplot(db_resurv, aes(RS_PROJTYP, fill=`Manipulate (y/n)`)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Resurvey project type") + coord_flip() +
theme(legend.position = "top")
ggsave(filename=here("output", "figures","issue11.tiff"),
width=18,height=8,units="cm",dpi=300)

ISSUE 12: Column RS_DUPL
db_resurv %>% filter(!is.na(RS_DUPL)) %>% select(RS_CODE, RS_DUPL) %>%
distinct()
ISSUE 13: Location uncertainty
db_resurv <- db_resurv %>%
# Redefine precision_new, which was wrong
mutate(precision_new = factor(ifelse(is.na(Lon_prec) & is.na(Lat_prec),
0, 1)))
ggplot(db_resurv, aes(`Location uncertainty (m)`, fill = precision_new)) +
geom_histogram( color = "black") +
xlab("Location uncertainty (m)")

ggplot(db_resurv %>% filter(`Location uncertainty (m)` <= 500),
aes(`Location uncertainty (m)`, fill = precision_new)) +
geom_histogram(color = "black") +
xlab("Location uncertainty (m) <= 500")
ggsave(filename=here("output", "figures","issue13_1.tiff"),
width=18,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(`Location uncertainty (m)` > 500),
aes(`Location uncertainty (m)`, fill = precision_new)) +
geom_histogram(color = "black") +
xlab("Location uncertainty (m) > 500")
ggsave(filename=here("output", "figures","issue13_2.tiff"),
width=18,height=8,units="cm",dpi=300)

NO ISSUES FROM HERE
Altitude and slope values
Unique slope values:
unique((db_resurv)$`Slope (°)`) %>% str_sort()
[1] "_" "-" "-1" "." "0" "0." "00" "03" "07" "1" "1." "10" "11" "12" "13"
[16] "14" "15" "16" "17" "18" "19" "2" "2." "20" "21" "22" "23" "24" "25" "26"
[31] "27" "28" "29" "3" "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" "4"
[46] "4." "40" "41" "42" "43" "44" "45" "46" "47" "48" "5" "5." "50" "51" "52"
[61] "55" "58" "6" "6." "60" "65" "7" "70" "75" "77" "78" "8" "8." "80" "85"
[76] "9" "9." "90" "95" NA
Set altitude, slope and aspect as numeric:
db_resurv <- db_resurv %>%
mutate(
# Some altitude values have a "-" after the number,
# convert to numeric after removing that
Altitude = as.numeric(gsub("-", "", Altitude)),
# Some slope values are noted as "_" or "-", these should be NA,
# otherwise convert to numeric
`Slope (°)` = ifelse(`Slope (°)` == "_" | `Slope (°)` == "-",
NA, as.numeric(`Slope (°)`)),
# Convert aspect values to numeric
`Aspect (°)` = as.numeric(`Aspect (°)`)
)
Aviso: There was 1 warning in `mutate()`.
ℹ In argument: `Slope (°) = ifelse(`Slope (°)` == "_" | `Slope (°)` == "-", NA,
as.numeric(`Slope (°)`))`.
Caused by warning in `ifelse()`:
! NAs introducidos por coerción
Histograms:
ggplot(db_resurv, aes(Altitude)) +
geom_histogram(fill = "white", color = "black")

ggplot(db_resurv, aes(`Aspect (°)`)) +
geom_histogram(fill = "white", color = "black")

ggplot(db_resurv, aes(`Slope (°)`)) +
geom_histogram(fill = "white", color = "black")
ggsave(filename=here("output", "figures","issue8.tiff"),
width=18,height=10,units="cm",dpi=300)

range(db_resurv$`Slope (°)`, na.rm=T)
[1] -1 95
Add columns date and year
db_resurv <- db_resurv %>%
mutate(date = dmy(`Date of recording`), year = year(date))
Histograms:
ggplot(db_resurv, aes(year)) + geom_histogram(fill = "white", color = "black")

Plot size
ggplot(db_resurv, aes(`Relevé area (m²)`)) +
geom_histogram(fill = "white", color = "black")

Observations with no info on plot size:
nrow(db_resurv %>% filter(is.na(`Relevé area (m²)`)))
[1] 19599
Cover values (total, trees, shrubs, herbs, mossess)
db_resurv %>%
pivot_longer(cols = c(`Cover total (%)`, `Cover tree layer (%)`,
`Cover shrub layer (%)`, `Cover herb layer (%)`,
`Cover moss layer (%)`),
names_to = "variable", values_to = "value") %>%
ggplot(aes(x = value)) +
geom_histogram(fill = "white", color = "black", bins = 10) +
facet_wrap(~ variable, scales = "free") +
labs(x = "Value", y = "Frequency")

db_resurv %>%
reframe(across(c(`Cover total (%)`, `Cover tree layer (%)`,
`Cover shrub layer (%)`, `Cover herb layer (%)`,
`Cover moss layer (%)`), ~range(., na.rm = TRUE)))
All values OK.
Mosses and lichens identified
ggplot(db_resurv, aes(`Mosses identified (Y/N)`)) + geom_bar()

ggplot(db_resurv, aes(`Lichens identified (Y/N)`)) + geom_bar()

NA in most cases.
All resurveys for each resurvey plot to send to Bea
db_Europa<- db_resurv %>%
group_by(RS_CODE, `ReSurvey site`,
# If ReSurvey plot is not NA,
# group by RS_CODE, `ReSurvey site`, `ReSurvey plot`
`ReSurvey plot` = ifelse(is.na(`ReSurvey plot`),
NA_character_, `ReSurvey plot`),
# If ReSurvey plot is NA, group by coordinates
# Create a unique grouping variable that uses coordinates
# only when conditions are met
group_coords = ifelse(is.na(`ReSurvey plot`),
paste(Lon_updated, Lat_updated), NA_character_)
) %>%
# Add unique identifiers for each plot.
# These are based on the unique combination of RS_CODE, ReSurvey site and
# ReSurvey plot (When ReSurvey plot is not NA)
# and on the unique combination of RS_CODE, ReSurvey site
# and updated coordinates (When ReSurvey plot is NA)
mutate(plot_unique_id = cur_group_id()) %>%
select(PlotObservationID, Country, `Date of recording`, RS_CODE,
`ReSurvey site`, `ReSurvey plot`, Lon_updated, Lat_updated,
group_coords, `Expert System`, `Location method`, plot_unique_id) %>%
ungroup() %>%
# Convert dates to date format and get the year
mutate(date = dmy(`Date of recording`), year = year(date)) %>%
select(-`Date of recording`, -date, -group_coords) %>%
# Add unique identifiers for each observation
mutate(obs_unique_id = row_number())
print(db_Europa, width = Inf)
Save to csv (file for us):
write_csv(db_Europa,here("data", "clean","db_Europa_20250107.csv"))
Save to csv (file for Bea, with only essential info):
write_csv(db_Europa %>%
select(obs_unique_id, plot_unique_id, Lon_updated, Lat_updated,
year),
here("data", "clean","db_Europa_20241210_short.csv"))
Info on HabitatID from DK
Based on information got from Jesper.
Read the data sent by Jesper from DK
db_DK_J<-read_tsv(here("data", "raw",
"DK_Naturdata_Res_habitat_hab_codes_Jesper",
"DK_Naturdata_Res_habitat_hab_codes.txt"))
Rows: 158800 Columns: 9── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (2): HABITAT, Dataset
dbl (7): PlotObservationID, RELEVE_NR, CIRC_ID, PLOT5_ID, PLOT15_ID, Access ...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Add info on HabitatID to db_resurv
db_resurv <- db_resurv %>%
# Keeping all obs in db_resurv but not all in db_DK_J
left_join(db_DK_J %>% select(PlotObservationID, HabitatID))
Joining with `by = join_by(PlotObservationID)`
List of HabitatID
print(db_resurv %>% distinct(HabitatID), n = 100)
Write csv:
write_csv(db_resurv %>% distinct(HabitatID),
here("data", "clean","list_HabitatID_DK_resurv.csv"))
Cases without HabitatID info
Cases without ESy EUNIS habitat:
nrow(db_resurv %>% filter(is.na(`Expert System`)))/nrow(db_resurv)
[1] 0.6814982
Cases without ESy EUNIS habitat but with HabitatID from DK:
nrow(db_resurv %>% filter(is.na(`Expert System`)&!is.na(HabitatID)))/nrow(db_resurv)
[1] 0.2727117
Cases without ESy EUNIS habitat and without HABITAT from DK:
nrow(db_resurv %>%
filter(is.na(`Expert System`)&is.na(HabitatID)))/nrow(db_resurv)
[1] 0.4087865
Cases without ESy EUNIS habitat and without HabitatID from DK where
data is presence / absence:
nrow(db_resurv %>%
filter(is.na(`Expert System`) &
is.na(HabitatID) &
`Cover abundance scale` == "Presence/Absence"))/
nrow(db_resurv)
[1] 0.1655851
Cases without ESy EUNIS habitat and without HabitatID from DK where
data is not presence / absence:
nrow(db_resurv %>%
filter(is.na(`Expert System`) &
is.na(HabitatID) &
`Cover abundance scale` != "Presence/Absence"))/
nrow(db_resurv)
[1] 0.2432014
Change some Annex I habitat codes that were wrong
db_resurv <- db_resurv %>%
mutate(HabitatID = as.character(HabitatID)) %>%
mutate(HabitatID = ifelse(HabitatID == "9998", "91D0",
ifelse(HabitatID == "9999", "91E0", HabitatID)))
Add info on correspondences HabitatID (DK, Jesper) - EUNIS
Read correspondences file:
correspondences<-read_excel(here("data", "edited",
"correspondence_HabitatID_DK.xlsx"))
Add info to db_resurv:
db_resurv <- db_resurv %>%
# Keeping all obs in db_resurv but not all in db_DK_J
left_join(correspondences %>% select(HabitatID, EUNIS))
Joining with `by = join_by(HabitatID)`
Correct NA values in EUNIS
db_resurv <- db_resurv %>%
mutate(EUNIS = ifelse(EUNIS == "NA", NA, EUNIS))
Add info on EUNIS (DK) to EUNISa:
db_resurv <- db_resurv %>%
mutate(EUNISa =
# If EUNIS (DK) is available, add as EUNISa
ifelse(!is.na(EUNIS), EUNIS,
# Otherwise keep EUNISa
EUNISa),
EUNIS_assignation = ifelse(!is.na(EUNIS), "Info from DK",
ifelse(is.na(EUNISa), "Not possible",
"Expert system"))) %>%
# Remove column EUNIS (DK)
select(-EUNIS)
ggplot(db_resurv, aes(EUNIS_assignation)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS assignation")

Generate file for ISSUE 5
write_csv(db_resurv %>%
filter(is.na(`Expert System`)&is.na(HabitatID)),
here("output", "csv","issue5.csv"))
Update columns for EUNIS levels and descriptions
Update the columns for the different EUNISs levels:
db_resurv <- db_resurv %>%
mutate(
# EUNISa levels
EUNISa_1 = substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 2, 1)),
EUNISa_2 = ifelse(
nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 3, 2),
substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 3, 2)),
NA_character_
),
EUNISa_3 = ifelse(
nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 4, 3),
substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 4, 3)),
NA_character_
),
EUNISa_4 = ifelse(
nchar(EUNISa) >= ifelse(str_starts(EUNISa, "MA"), 5, 4),
substr(EUNISa, 1, ifelse(str_starts(EUNISa, "MA"), 5, 4)),
NA_character_
)
) %>%
# Remove HabitatID column
select(-HabitatID)
Update columns with descriptions for the level 1 codes:
db_resurv <- db_resurv %>%
mutate(
EUNISa_1_descr = case_when(
EUNISa_1 == "V" ~ "Vegetated man-made habitats",
EUNISa_1 == "U" ~ "Inland habitats with no or little soil",
EUNISa_1 == "T" ~ "Forests and other wooded land",
EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISa_1 == "R" ~ "Grasslands",
EUNISa_1 == "Q" ~ "Wetlands",
EUNISa_1 == "P" ~ "Inland waters",
EUNISa_1 == "N" ~ "Coastal habitats",
EUNISa_1 == "MA" ~ "Marine habitats",
TRUE ~ NA_character_
)
)
Number of different EUNIS codes
Recalculate how many different EUNIS codes have been assigned:
db_resurv <- db_resurv %>%
mutate(
# Count the number of non-NA values across the EUNIS columns
n_EUNIS = rowSums(!is.na(select(., EUNISa:EUNISd)))
)
ggplot(db_resurv, aes(n_EUNIS)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Number of differnt EUNIS codes assigned") + coord_flip()

ggplot(db_resurv %>% filter(n_EUNIS > 0), aes(n_EUNIS)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "Number of differnt EUNIS codes assigned") + coord_flip()

New plot for EUNISa_1 (the first assigned EUNIS in cases of multiple
assignations, level 1):
ggplot(db_resurv, aes(EUNISa_1_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 1") + coord_flip()

ggplot(db_resurv %>% filter(!is.na(EUNISa_1_descr)), aes(EUNISa_1_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 1") + coord_flip()

Add info on descriptions for EUNIS levels 2-4
descriptions<-read_excel(here("data", "edited",
"EUNIS-Habitats-2021-06-01_modified.xlsx"))
# Define the columns and corresponding description column names
eunis_cols <- c("EUNISa_2", "EUNISa_3", "EUNISa_4",
"EUNISb_2", "EUNISb_3", "EUNISb_4",
"EUNISc_2", "EUNISc_3", "EUNISc_4",
"EUNISd_2", "EUNISd_3", "EUNISd_4")
# Create corresponding description column names
descr_col_names <- paste0(eunis_cols, "_descr")
# Use reduce to loop through the columns and join dynamically based on level
db_resurv <- reduce(seq_along(eunis_cols), function(data, i) {
# Extract level number from the column name (e.g., EUNISa_2 -> 2)
level <- as.numeric(gsub("\\D", "", eunis_cols[i]))
# Filter descriptions for the corresponding level
descriptions_level <- descriptions %>%
filter(level == level) %>%
select(`EUNIS 2020 code`, `EUNIS-2021 habitat name`)
# Perform the left_join and rename the column dynamically
data %>%
left_join(
descriptions_level,
by = setNames("EUNIS 2020 code", eunis_cols[i])
) %>%
rename(!!descr_col_names[i] := `EUNIS-2021 habitat name`)
}, .init = db_resurv)
The matching did not work sometimes, correct!
# Correct EUNISa levels 2-4 descriptions
db_resurv <- db_resurv %>%
mutate(EUNISa_2_descr =
ifelse(!is.na(EUNISa_2_descr), EUNISa_2_descr,
case_when(
EUNISa_2 == "Pf" ~ "Fresh-water submerged vegetation",
EUNISa_2 == "Pj" ~ "Stonewort vegetation",
EUNISa_2 == "R4" ~ "Alpine and subalpine grasslands",
EUNISa_2 == "Pb" ~ "Calcareous spring and spring brook",
EUNISa_2 == "Qb" ~ "Wetlands",
EUNISa_2 == "R3" ~ "Seasonally wet and wet grasslands",
EUNISa_2 == "Qa" ~ "Mires",
EUNISa_2 == "Pa" ~ "Base-poor spring and spring brook",
EUNISa_2 == "Ph" ~ "Oligotrophic-water vegetation",
EUNISa_2 == "Pg" ~ "Fresh-water nymphaeid vegetation",
EUNISa_2 ==
"Pd" ~ "Fresh-water small pleustophyte vegetation",
EUNISa_2 == "Pc" ~ "Brackish-water vegetation",
EUNISa_2 ==
"Pe" ~ "Fresh-water large pleustophyte vegetation",
EUNISa_2 == "Pi" ~ "Dystrophic-water vegetation",
EUNISa_2 == "S1" ~ "Tundra",
EUNISa_2 ==
"U7" ~ "Unvegetated or sparsely vegetated gravel bars",
EUNISa_2 == "Q6" ~ "Periodically exposed shores",
TRUE ~ NA_character_)
),
EUNISa_3_descr =
ifelse(!is.na(EUNISa_3_descr), EUNISa_3_descr,
case_when(
EUNISa_3 =="U71" ~ "Unvegetated or sparsely vegetated gravel bar in montane and alpine regions",
EUNISa_3 =="Q61" ~ "Periodically exposed shore with stable, eutrophic sediments with pioneer or ephemeral vegetation",
EUNISa_3 =="Q62" ~ "Periodically exposed shore with stable, mesotrophic sediments with pioneer or ephemeral vegetation",
TRUE ~ NA_character_
))
)
# Correct EUNISb levels 2-4 descriptions
db_resurv <- db_resurv %>%
mutate(EUNISb_2_descr =
ifelse(!is.na(EUNISb_2_descr), EUNISb_2_descr,
case_when(
EUNISb_2 == "Pj" ~ "Stonewort vegetation",
EUNISb_2 == "R4" ~ "Alpine and subalpine grasslands",
EUNISb_2 == "Pf" ~ "Fresh-water submerged vegetation",
TRUE ~ NA_character_)
)
)
EUNISc and EUNISd levels 2-4 are OK.
Notes EUNIS codes - to change?
https://www.sci.muni.cz/botany/chytry/Schaminee_etal2021_EEA-Report-Aquatic-Wetland-habitats.pdf
EUNISa_2 == “Q6” : “Periodically exposed shores” EUNISa_3 = “Q61” :
“Periodically exposed shore with stable, eutrophic sediments with
pioneer or ephemeral vegetation” EUNISa_3 == “Q62” : “Periodically
exposed shore with stable, mesotrophic sediments with pioneer or
ephemeral vegetation”
This classification of Q + numbers is now coexisting in the database
with Qa & Qb (metadata). How to proceed?
db_resurv %>% filter(EUNISa_1 == "Q") %>% distinct(EUNISa_2)
Plots of level-2 categories within each level 1 category
ggplot(db_resurv %>% filter(EUNISa_1 == "MA"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "MA") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","MA_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "N"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "N") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","N_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "P"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "P") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","P_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "Q"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "Q") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","Q_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "R"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "R") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","R_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "S"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "S") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","S_level2.tiff"),
width=16,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "T"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "T") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","T_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "U"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "U") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","U_level2.tiff"),
width=16,height=8,units="cm",dpi=300)

ggplot(db_resurv %>% filter(EUNISa_1 == "V"), aes(EUNISa_2_descr)) +
geom_bar(aes(y = (..count..) / sum(..count..) * 100)) +
labs(y = "Percentage of ReSurvey observations",
x = "EUNIS level 2") + coord_flip() +
ggtitle(db_resurv %>% filter(EUNISa_1 == "V") %>% distinct(EUNISa_1_descr))
ggsave(filename=here("output", "figures","V_level2.tiff"),
width=14,height=8,units="cm",dpi=300)

Save to clean data
Save so-far clean datafile for resurvey database:
write_tsv(db_resurv,here("data", "clean","db_resurv_clean.csv"))
Session info
sessionInfo()
R version 4.4.2 (2024-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 10 x64 (build 19045)
Matrix products: default
locale:
[1] LC_COLLATE=Spanish_Spain.utf8 LC_CTYPE=Spanish_Spain.utf8
[3] LC_MONETARY=Spanish_Spain.utf8 LC_NUMERIC=C
[5] LC_TIME=Spanish_Spain.utf8
time zone: Europe/Paris
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] readxl_1.4.3 purrr_1.0.2 stringr_1.5.1 ggplot2_3.5.1
[5] lubridate_1.9.4 dplyr_1.1.4 tidyr_1.3.1 readr_2.1.5
[9] here_1.0.1
loaded via a namespace (and not attached):
[1] sass_0.4.9 utf8_1.2.4 generics_0.1.3 stringi_1.8.4
[5] hms_1.1.3 digest_0.6.37 magrittr_2.0.3 evaluate_1.0.1
[9] grid_4.4.2 timechange_0.3.0 fastmap_1.2.0 cellranger_1.1.0
[13] rprojroot_2.0.4 jsonlite_1.8.9 fansi_1.0.6 scales_1.3.0
[17] jquerylib_0.1.4 cli_3.6.3 rlang_1.1.4 crayon_1.5.3
[21] bit64_4.5.2 munsell_0.5.1 withr_3.0.2 cachem_1.1.0
[25] yaml_2.3.10 tools_4.4.2 parallel_4.4.2 tzdb_0.4.0
[29] colorspace_2.1-1 vctrs_0.6.5 R6_2.5.1 lifecycle_1.0.4
[33] bit_4.5.0.1 vroom_1.6.5 pkgconfig_2.0.3 pillar_1.9.0
[37] bslib_0.8.0 gtable_0.3.6 glue_1.8.0 xfun_0.49
[41] tibble_3.2.1 tidyselect_1.2.1 knitr_1.49 farver_2.1.2
[45] htmltools_0.5.8.1 rmarkdown_2.29 labeling_0.4.3 compiler_4.4.2
LS0tDQp0aXRsZTogIlNjcmlwdCB0byBtYWtlIGEgZmlyc3QgY2hlY2sgb2YgdGhlIEVWQSBSZVN1cnZleSBkYXRhYmFzZSBmb3IgTU9USVZBVEUiDQpvdXRwdXQ6DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQotLS0NCg0KIyBMb2FkIGxpYnJhcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkocmVhZHhsKQ0KYGBgDQoNCiMgUmVhZCB0aGUgZGF0YQ0KDQpgYGB7cn0NCmRiX3Jlc3VydjwtcmVhZF90c3YoaGVyZSgiZGF0YSIsICJlZGl0ZWQiLCAiZGJfcmVzdXJ2LmNzdiIpKQ0KYGBgDQoNCiMgUHJvYmxlbXMgKGRvIG5vdCBhZmZlY3QgdXMpDQoNCmBgYHtyfQ0KcHJvYmxlbXM8LXByb2JsZW1zKGRiX3Jlc3VydikNCnNvcnQodW5pcXVlKHByb2JsZW1zJGNvbCkpDQpuYW1lcyhkYl9yZXN1cnZbYyg3LDEzKV0pDQpgYGANCg0KTm8gcHJvYmxlbXMgcmVhbGx5IQ0KDQojIFVwZGF0ZSBjb29yZGluYXRlcw0KDQpDcmVhdGUgbmV3IGNvbHVtbiB3aXRoIG9sZCBjb29yZGluYXRlcyBpZiBuZXcgbm90IGF2YWlsYWJsZSwgYW5kIHdpdGggbmV3IGlmIGF2YWlsYWJsZS4NCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoTG9uX3VwZGF0ZWQgPSBpZmVsc2UoaXMubmEoTG9uX3ByZWMpLExvbmdpdHVkZSxMb25fcHJlYyksDQogICAgICAgICBMYXRfdXBkYXRlZCA9IGlmZWxzZShpcy5uYShMYXRfcHJlYyksTGF0aXR1ZGUsTGF0X3ByZWMpKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQoZGJfcmVzdXJ2LCB3aWR0aCA9IEluZikNCmBgYA0KDQojIElTU1VFIDE6IFJlU3VydmV5IHBsb3QgaXMgTkENCg0KQ2FyZWZ1bCEgU29tZXRpbWVzIFJlU3VydmV5IHBsb3QgaXMgTkEuIA0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2JT4lZmlsdGVyKGlzLm5hKFJTX0NPREUpKSkNCm5yb3coZGJfcmVzdXJ2JT4lZmlsdGVyKGlzLm5hKGBSZVN1cnZleSBzaXRlYCkpKQ0KbnJvdyhkYl9yZXN1cnYlPiVmaWx0ZXIoaXMubmEoYFJlU3VydmV5IHBsb3RgKSkpDQpgYGANCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoaXMubmEoYFJlU3VydmV5IHBsb3RgKSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlMS5jc3YiKSkNCmBgYA0KDQojIElTU1VFIDI6IENvb3JkaW5hdGVzIGFyZSBOQQ0KDQpDb29yZGluYXRlcyBhcmUgYWxzbyBOQSBpbiBzb21lIGNhc2VzLg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2JT4lZmlsdGVyKGlzLm5hKExvbl91cGRhdGVkKSAmIGlzLm5hKExhdF91cGRhdGVkICkpKQ0KYGBgDQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGRiX3Jlc3VydiAlPiUgZmlsdGVyKGlzLm5hKExvbl91cGRhdGVkKSAmIGlzLm5hKExhdF91cGRhdGVkKSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlMi5jc3YiKSkNCmBgYA0KDQpCdXQgUmVzdXJ2ZXkgcGxvdCBhbmQgY29vcmRpbmF0ZXMgYXJlIG5ldmVyIG1pc3NpbmcgYXQgdGhlIHNhbWUgdGltZS4NCg0KYGBge3J9DQpucm93KGRiX3Jlc3VydiU+JQ0KICAgICAgIGZpbHRlcihpcy5uYShgUmVTdXJ2ZXkgcGxvdGApICYgaXMubmEoTG9uX3VwZGF0ZWQpICYgaXMubmEoTGF0X3VwZGF0ZWQpKSkNCmBgYA0KDQojIElTU1VFIDM6IERpZmZlcmVudCBSZVN1cnZleSBvYnNlcnZhdGlvbnMgd2l0aGluIHRoZSBzYW1lIHBsb3QgaGF2ZSBkaWZmZXJlbnQgY29vcmRpbmF0ZXMNCg0KQ2FyZWZ1bCEgSW4gc29tZSBjYXNlcyBkaWZmZXJlbnQgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIHdpdGhpbiB0aGUgc2FtZSBwbG90IGhhdmUgZGlmZmVyZW50IGNvb3JkaW5hdGVzLiANCg0KQ3JlYXRlIHR3byBuZXcgY29sdW1ucyBpbiBkYl9yZXN1cnY6IGNvb3JkaW5hdGVzX2VxdWFsIGluZGljYXRpbmcgaWYgY29vcmRpbmF0ZXMgYXJlIGV4YWN0bHkgZXF1YWwgYmV0d2VlbiBSZVN1cnZleSBvYnNlcnZhdGlvbnMsIGFuZCBjb29yZGluYXRlc19jb25zaXN0ZW50LCBpbmRpY2F0aW5nIGlmIGNvb3JkaW5hdGVzIGFyZSBjb25zaXN0ZW50IGJldHdlZW4gUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIChjb25zaXN0ZW50IG1lYW5pbmcgdGhhdCBkaWZmZXJlbmNlIDwgMC4wMDEgZGVncmVlcykuDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSB0aHJlc2hvbGQgKGUuZy4sIDAuMDAxIGRlZ3JlZXMgZm9yIGxvbmdpdHVkZS9sYXRpdHVkZSBkaWZmZXJlbmNlcykNCnRocmVzaG9sZCA8LSAwLjAwMQ0KDQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBncm91cF9ieShSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCkgJT4lDQogIG11dGF0ZSgNCiAgICBsb25fcmFuZ2UgPSBpZmVsc2UoYWxsKGlzLm5hKExvbl91cGRhdGVkKSksIE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4KExvbl91cGRhdGVkLCBuYS5ybSA9IFQpIC0gDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluKExvbl91cGRhdGVkLCBuYS5ybSA9IFQpKSwNCiAgICBsYXRfcmFuZ2UgPSBpZmVsc2UoYWxsKGlzLm5hKExhdF91cGRhdGVkKSksIE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4KExhdF91cGRhdGVkLCBuYS5ybSA9IFQpIC0gDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFQpKSwNCiAgICBjb29yZGluYXRlc19lcXVhbCA9IGlmZWxzZShpcy5uYShMb25fdXBkYXRlZCkgJiBpcy5uYShMYXRfdXBkYXRlZCksIE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvbl9yYW5nZSA9PSAwICYgbGF0X3JhbmdlID09IDApLA0KICAgIGNvb3JkaW5hdGVzX2NvbnNpc3RlbnQgPSBpZmVsc2UoaXMubmEoTG9uX3VwZGF0ZWQpICYgaXMubmEoTGF0X3VwZGF0ZWQpLCBOQSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvbl9yYW5nZSA8IHRocmVzaG9sZCAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXRfcmFuZ2UgPCB0aHJlc2hvbGQpDQogICkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KC1sb25fcmFuZ2UsIC1sYXRfcmFuZ2UpDQpgYGANCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoY29vcmRpbmF0ZXNfZXF1YWw9PUZBTFNFKSwNCiAgICAgICAgICBoZXJlKCJvdXRwdXQiLCAiY3N2IiwiaXNzdWUzLmNzdiIpKQ0KYGBgDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2ICU+JSANCiAgZ3JvdXBfYnkoUlNfQ09ERSxgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCkgJT4lDQogIHN1bW1hcml6ZShpc19lcXVhbCA9IGFsbChjb29yZGluYXRlc19lcXVhbCksDQogICAgICAgICAgICBpc19jb25zaXN0ZW50ID0gYWxsKGNvb3JkaW5hdGVzX2NvbnNpc3RlbnQpLA0KICAgICAgICAgICAgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIG11dGF0ZShjb29yZGluYXRlX3N0YXR1cyA9IGNhc2Vfd2hlbigNCiAgICBpc19lcXVhbCB+ICJFcXVhbCIsDQogICAgIWlzX2VxdWFsICYgaXNfY29uc2lzdGVudCB+ICJDb25zaXN0ZW50ICg8IDAuMDAxwropIiwNCiAgICAhaXNfZXF1YWwgJiAhaXNfY29uc2lzdGVudCB+ICJJbmNvbnNpc3RlbnQgKD4gMC4wMDHCuikiKSkgJT4lDQogIGNvdW50KGNvb3JkaW5hdGVfc3RhdHVzKSU+JQ0KICBtdXRhdGUocGVyY2VudGFnZSA9IG4gLyBzdW0obikgKiAxMDApICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBwZXJjZW50YWdlLCB5ID0gY29vcmRpbmF0ZV9zdGF0dXMsIGZpbGwgPSBjb29yZGluYXRlX3N0YXR1cykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZTAocm91bmQocGVyY2VudGFnZSwgMSksICIlIikpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIHNpemUgPSAzKSArIA0KICBsYWJzKHggPSAiUGVyY2VudGFnZSBvZiBQbG90cyIsIHkgPSBOVUxMKSArDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWUzLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xMCxoZWlnaHQ9Nyx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KIyBJU1NVRSA0OiBTb21lIHBsb3RzIGhhdmUgb25seSBvbmUgcmVzdXJ2ZXkNCg0KV2hlbiBSZVN1cnZleSBwbG90IGlzIG5vdCBOQSwgdXNlIHRoZSB1bmlxdWUgY29tYmluYXRpb24gb2YgUlNfQ09ERSwgUmVTdXJ2ZXkgc2l0ZSBhbmQgUmVTdXJ2ZXkgcGxvdCB0byB1bmlxdWVseSBkZWZpbmUgZWFjaCBSZVN1cnZleSBwbG90IChhcyBkZWZpbmVkIGluIG1ldGFkYXRhKS4gV2hlbiBSZVN1cnZleSBwbG90IGlzIE5BICgxMTEgcm93cyksIHVzZSB0aGUgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIFJTX0NPREUsIFJlU3VydmV5IHNpdGUgYW5kIHVwZGF0ZWQgY29vcmRpbmF0ZXMgdG8gdW5pcXVlbHkgZGVmaW5lIGVhY2ggUmVTdXJ2ZXkgcGxvdC4gQ2hlY2sgaG93IG1hbnkgcmVzdXJ2ZXlzIChpLmUuIGRpZmZlcmVudCB5ZWFycyBhcmUgdGhlcmUgZm9yIGVhY2ggdW5pcXVlIGNvbWJpbmF0aW9uKS4NCg0KYGBge3J9DQpjb3VudF9yZXN1cnZleXMgPC0gZGJfcmVzdXJ2ICU+JQ0KICAjIENvbnZlcnQgZGF0ZXMgdG8gZGF0ZSBmb3JtYXQgYW5kIGdldCB0aGUgeWVhcg0KICBtdXRhdGUoZGF0ZSA9IGRteShgRGF0ZSBvZiByZWNvcmRpbmdgKSwgeWVhciA9IHllYXIoZGF0ZSkpICU+JQ0KICBncm91cF9ieShSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsDQogICAgICAgICAgICMgSWYgUmVTdXJ2ZXkgcGxvdCBpcyBub3QgTkEsIA0KICAgICAgICAgICAjIGdyb3VwIGJ5IFJTX0NPREUsIGBSZVN1cnZleSBzaXRlYCwgYFJlU3VydmV5IHBsb3RgDQogICAgICAgICAgIGBSZVN1cnZleSBwbG90YCA9IGlmZWxzZShpcy5uYShgUmVTdXJ2ZXkgcGxvdGApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BX2NoYXJhY3Rlcl8sIGBSZVN1cnZleSBwbG90YCksDQogICAgICAgICAgICMgSWYgUmVTdXJ2ZXkgcGxvdCBpcyBOQSwgZ3JvdXAgYnkgY29vcmRpbmF0ZXMNCiAgICAgICAgICAgTG9uX3VwZGF0ZWQgPSBpZmVsc2UoaXMubmEoYFJlU3VydmV5IHBsb3RgKSwgTG9uX3VwZGF0ZWQsIE5BX3JlYWxfKSwNCiAgICAgICAgICAgTGF0X3VwZGF0ZWQgPSBpZmVsc2UoaXMubmEoYFJlU3VydmV5IHBsb3RgKSAsIExhdF91cGRhdGVkLCBOQV9yZWFsXykNCiAgKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgICMgR2V0IGhvdyBtYW55IGRpZmZlcmVudCB5ZWFycyBmb3IgZWFjaCB1bmlxdWUgZ3JvdXANCiAgICBkaXN0aW5jdF95ZWFycz1uX2Rpc3RpbmN0KHllYXIpLCANCiAgICAjIEdldCBob3cgbWFueSBkaWZmZXJlbnQgZGF0ZXMgZm9yIGVhY2ggdW5pcXVlIGdyb3VwDQogICAgZGlzdGluY3RfZGF0ZXM9bl9kaXN0aW5jdChkYXRlKSwgLmdyb3VwcyA9ICJkcm9wIikNCmBgYA0KDQpTdW1tYXJ5IHN0YXRzOg0KDQpgYGB7cn0NCnN1bW1hcnkoY291bnRfcmVzdXJ2ZXlzJGRpc3RpbmN0X3llYXJzKQ0Kc2QoY291bnRfcmVzdXJ2ZXlzJGRpc3RpbmN0X3llYXJzKQ0KYGBgDQoNCkhpc3RvZ3JhbXM6DQoNCmBgYHtyfQ0KIyBGb3IgYWxsIGRhdGENCmdncGxvdChjb3VudF9yZXN1cnZleXMsIGFlcyh4ID0gZGlzdGluY3RfeWVhcnMpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siLCBiaW5zID0gNTUpKw0KICB4bGFiKCJOdW1iZXIgb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIChkaWZmZXJlbnQgeWVhcnMpIikgKw0KICB5bGFiKCJOdW1iZXIgb2YgcGxvdHMiKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWU0LnRpZmYiKSwNCiAgICAgICB3aWR0aD0xMSxoZWlnaHQ9Nyx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KTnVtYmVyIGFuZCBwcm9wb3J0aW9uIG9mIHBsb3RzIHdpdGggb25seSAxIHJlc3VydmV5IChzaG91bGQgbm90IGJlIHNvISkNCg0KYGBge3J9DQpucm93KGNvdW50X3Jlc3VydmV5cyU+JWZpbHRlcihkaXN0aW5jdF95ZWFycz09MSkpDQpucm93KGNvdW50X3Jlc3VydmV5cyU+JWZpbHRlcihkaXN0aW5jdF95ZWFycz09MSkpL25yb3coY291bnRfcmVzdXJ2ZXlzKQ0KYGBgDQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGNvdW50X3Jlc3VydmV5cyU+JWZpbHRlcihkaXN0aW5jdF95ZWFycz09MSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlNC5jc3YiKSkNCmBgYA0KDQojIElTU1VFIDU6IERhdGFzZXRzIHdpdGggb25seSBwcmVzZW5jZS9hYnNlbmNlDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2ICU+JQ0KICBmaWx0ZXIoYENvdmVyIGFidW5kYW5jZSBzY2FsZWA9PSJQcmVzZW5jZS9BYnNlbmNlIikgJT4lDQogIGRpc3RpbmN0KERhdGFzZXQpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSANCiAgICAgICAgIG11dGF0ZShwcmVzX29yX2FiID1pZmVsc2UoYENvdmVyIGFidW5kYW5jZSBzY2FsZWA9PSJQcmVzZW5jZS9BYnNlbmNlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlByZXNlbmNlL0Fic2VuY2UiLCAiQWJ1bmRhbmNlIiksDQogICAgICAgICAgICAgICAgREtfTmF0dXJkYXRhX1JlcyA9IGlmZWxzZShEYXRhc2V0ID09ICJES19OYXR1cmRhdGFfUmVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJZIiwgIk4iKSksDQogICAgICAgICAgICAgICAgYWVzKHByZXNfb3JfYWIsIGZpbGwgPSBES19OYXR1cmRhdGFfUmVzKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSIsIHggPSBOVUxMKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWU1LnRpZmYiKSwNCiAgICAgICB3aWR0aD0xMixoZWlnaHQ9Nyx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KDQpGb3IgREtfTmF0dXJkYXRhX1JlcyAtIGluZm8gYWJvdXQgaGFiaXRhdCBmcm9tIEplcmtlcidzIGZpbGUgKHNlZSBiZWxvdykuDQoNCiMgSVNTVUUgNjogT2JzZXJ2YXRpb25zIHdpdGggd3JvbmcgY291bnRyeSAoR0lTKQ0KDQpSZWFkIHRleHQgZmlsZSB3cm9uZ19jb3VudHJpZXMgb2J0YWluZWQgaW4gQXJjR0lTOg0KDQpgYGB7cn0NCndyb25nX2NvdW50cmllcyA8LSByZWFkX2RlbGltKGhlcmUoImRhdGEiLCAiY2xlYW4iLCJ3cm9uZ19jb3VudHJpZXMudHh0IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxpbSA9ICI7IikNCmBgYA0KDQpgYGB7cn0NCndyaXRlX2Nzdih3cm9uZ19jb3VudHJpZXMsIGhlcmUoIm91dHB1dCIsICJjc3YiLCJpc3N1ZTYuY3N2IikpDQpgYGANCg0KIyBJU1NVRSA3OiBEaWZmZXJlbnQgY292ZXIgYWJ1bmRhbmNlIHNjYWxlcw0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhgQ292ZXIgYWJ1bmRhbmNlIHNjYWxlYCkpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwgeCA9ICJDb3ZlciBhYnVuZGFuY2Ugc2NhbGUiKSArDQogIGNvb3JkX2ZsaXAoKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWU3LnRpZmYiKSwNCiAgICAgICB3aWR0aD0xOCxoZWlnaHQ9MTAsdW5pdHM9ImNtIixkcGk9MzAwKQ0KYGBgDQoNCiMgSVNTVUUgODogV3JvbmcgRVVOSVMgY29kZXMNCg0KVXNlZCB0aGlzIGluZm8gaW4gbWV0YWRhdGEgZmlsZToNCg0KRXhwZXJ0IHN5c3RlbSBjbGFzc2lmaWNhdGlvbiB0byBFVU5JUyBoYWJpdGF0cyAoaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZHMvNDgxMjczNiA7IGh0dHBzOi8vZmxvcmF2ZWcuZXUvaGFiaXRhdC8pLiANCkkgYW0gc2VuZGluZyB5b3UgbGVnZW5kIGZvciBFVU5JUyBjbGFzc2lmaWNhdGlvbiB2ZXJzaW9uIDIwMjItMTAtMTYgd2l0aCBhbGwgY29kZXMgYW5kIG1lYW5pbmdzLCBkaXJlY3RseSBwcmVwYXJlZCBmcm9tIGV4cGVydCBzeXN0ZW0gZmlsZSAoc2Vjb25kIHNoZWV0KSAtIGl0IGlzIHNsaWdodGx5IGRpZmZlcmVudCBmcm9tIHB1Ymxpc2hlZCB2ZXJzaW9uIGluIFpFTk9ETyAoaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZHMvNDgxMjczNiAsIGxpdHRsZSBiaXQgb2xkIGRhdGVkIG5vdykgYW5kIGZyb20gaHR0cHM6Ly9mbG9yYXZlZy5ldS9oYWJpdGF0LyAobGl0dGxlIGJpdCBuZXdlciB0aGFuIGluIGN1cnJlbnQgRVZBIHZlcnNpb24pLg0KDQpRYSA9IG1pcmVzIGFuZCBRYiA9IHdldGxhbmRzDQpQIHVuaXRzIOKAkyBpbiBmbG9yYXZlZy5ldSB0aGVyZSBpcyBzbGlnaHRseSBkaWZmZXJlbnQgY2xhc3NpZmljYXRpb24gKGh0dHBzOi8vZmxvcmF2ZWcuZXUvaGFiaXRhdC9vdmVydmlldy9QKSwgYnV0IGluIEVWQSBpcyBzdGlsbCB0aGlzIGNsYXNzaWZpY2F0aW9uIG9mIFA6DQoNClAgU3VyZmFjZSB3YXRlcnMNClBhIEJhc2UtcG9vciBzcHJpbmcgYW5kIHNwcmluZyBicm9vaw0KUGIgQ2FsY2FyZW91cyBzcHJpbmcgYW5kIHNwcmluZyBicm9vaw0KUGMgQnJhY2tpc2gtd2F0ZXIgdmVnZXRhdGlvbg0KUGQgRnJlc2gtd2F0ZXIgc21hbGwgcGxldXN0b3BoeXRlIHZlZ2V0YXRpb24NClBlIEZyZXNoLXdhdGVyIGxhcmdlIHBsZXVzdG9waHl0ZSB2ZWdldGF0aW9uDQpQZiBGcmVzaC13YXRlciBzdWJtZXJnZWQgdmVnZXRhdGlvbg0KUGcgRnJlc2gtd2F0ZXIgbnltcGhhZWlkIHZlZ2V0YXRpb24NClBoIE9saWdvdHJvcGhpYy13YXRlciB2ZWdldGF0aW9uDQpQaSBEeXN0cm9waGljLXdhdGVyIHZlZ2V0YXRpb24NClBqIFN0b25ld29ydCB2ZWdldGF0aW9uIA0KDQpQcmVzZW5jZSBvZiDigJwh4oCdIHNpbXBseSBtZWFucyB0aGF0IGZvciBvbmUgdW5pdCB0aGVyZSBhcmUgdHdvIG9yIG1vcmUgZGlmZmVyZW50IGZvcm11bGFzLCBlLmcuIFIxMSBhbmQgUjExIS4gU28gaXQgaXMgb25seSB0ZWNobmljYWwgc3R1ZmYuDQoNCk11bHRpcGxlIGFzc2lnbm1lbnQgb2YgcmVsZXbDqSDigJMgbm8gcHJpb3JpdHksIGFscGhhYmV0aWNhbCBvcmRlciwgZS5nLiBOMTYhLFM2NixTODEgbWVhbnMgdGhhdCByZWxldsOpIGNhbiBiZSBhc3NpZ25lZCB0byBhbGwgMyB1bml0czogTjE2IE1lZGl0ZXJyYW5lYW4gYW5kIE1hY2Fyb25lc2lhbiBjb2FzdGFsIGR1bmUgZ3Jhc3NsYW5kIChncmV5IGR1bmUpLCBTNjYgTWVkaXRlcnJhbmVhbiBoYWxvLW5pdHJvcGhpbG91cyBzY3J1YiBhbmQgUzgxIENhbmFyaWFuIHhlcm9waHl0aWMgc2NydWINCg0KTm8gdmFsdWUgcHJlc2VudCBpbiBFeHBlcnQgU3lzdGVtIOKAkyByZWxldsOpIGRpZG7CtHQgZW50ZXIgZXhwZXJ0IHN5c3RlbSBjbGFzc2lmaWNhdGlvbiAoPSBpdCBtZWFucyB0aGF0IHNvbWUgcHJlcmVxdWlzaXRlcyBhcmUgbWlzc2luZykNCg0K4oCcfuKAnSDigJMgcmVsZXbDqSBlbnRlcmVkIGV4cGVydCBjbGFzc2lmaWNhdGlvbiBob3dldmVyIHdhcyBub3QgY2xhc3NpZmllZCB0byBhbnkgRVVOSVMgdW5pdA0KKw0KDQpDbGVhbiBpbmZvIG9uIEV4cGVydCBzeXN0ZW0gY29sdW1uIGFuZCBzZXBhcmF0ZSBpdCB3aGVuIHRoZXJlIGFyZSBzZXZlcmFsIGNvZGVzLg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIENsZWFuICdFeHBlcnQgU3lzdGVtJyBjb2x1bW4gYnkgcmVtb3ZpbmcgIiEiIGFuZCByZXBsYWNpbmcgIn4iIHdpdGggTkENCiAgICBgRXhwZXJ0IFN5c3RlbWAgPSBjYXNlX3doZW4oDQogICAgICBgRXhwZXJ0IFN5c3RlbWAgPT0gIn4iIH4gTkFfY2hhcmFjdGVyXywgICMgUmVwbGFjZSAifiIgd2l0aCBOQQ0KICAgICAgVFJVRSB+IHN0cl9yZXBsYWNlX2FsbChgRXhwZXJ0IFN5c3RlbWAsICIhIiwgIiIpICAjIFJlbW92ZSAiISINCiAgICApDQogICkgJT4lDQogICMgU2VwYXJhdGUgdGhlIHZhbHVlcyBpbiAnRXhwZXJ0IFN5c3RlbScgaW50byBtdWx0aXBsZSBjb2x1bW5zDQogIHNlcGFyYXRlKA0KICAgIGBFeHBlcnQgU3lzdGVtYCwNCiAgICBpbnRvID0gYygiRVVOSVNhIiwgIkVVTklTYiIsICJFVU5JU2MiLCAiRVVOSVNkIiksDQogICAgc2VwID0gIiwiLA0KICAgIGV4dHJhID0gImRyb3AiLCAgIyBEcm9wIGV4dHJhIHZhbHVlcyBpZiB0aGVyZSBhcmUgbW9yZSB0aGFuIGNvbHVtbnMNCiAgICBmaWxsID0gInJpZ2h0IiwgICAjIEZpbGwgbWlzc2luZyB2YWx1ZXMgd2l0aCBOQSBmb3IgY2FzZXMgd2l0aCBmZXdlciB2YWx1ZXMNCiAgICByZW1vdmUgPSBGQUxTRSAgICAjIEtlZXAgdGhlIG9yaWdpbmFsICdFeHBlcnQgU3lzdGVtJyBjb2x1bW4NCiAgKQ0KYGBgDQoNCkNhbGN1bGF0ZSBob3cgbWFueSBkaWZmZXJlbnQgRVVOSVMgY29kZXMgaGF2ZSBiZWVuIGFzc2lnbmVkOg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLU5BIHZhbHVlcyBhY3Jvc3MgdGhlIEVVTklTIGNvbHVtbnMNCiAgICBuX0VVTklTID0gcm93U3VtcyghaXMubmEoc2VsZWN0KC4sIHN0YXJ0c193aXRoKCJFVU5JUyIpKSkpDQogICkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhuX0VVTklTKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiTnVtYmVyIG9mIGRpZmZlcm50IEVVTklTIGNvZGVzIGFzc2lnbmVkIikgKyBjb29yZF9mbGlwKCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihuX0VVTklTID4gMCksIGFlcyhuX0VVTklTKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiTnVtYmVyIG9mIGRpZmZlcm50IEVVTklTIGNvZGVzIGFzc2lnbmVkIikgKyBjb29yZF9mbGlwKCkNCmBgYA0KDQpDb3JyZWN0IHNvbWUgRVVOSVMgY29kZXMgdGhhdCBhcmUgcHJvYmFibHkgd3Jvbmc6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgiRVVOSVMiKSwgfiBjYXNlX3doZW4oDQogICAgLiA9PSAiTjE2TSIgfiAiTjE2IiwNCiAgICAuID09ICJTYSIgfiAiVjQiLA0KICAgIC4gPT0gIlNiIiB+ICJWNSIsDQogICAgLiA9PSAiVDFDVCIgfiAiVDFDIiwNCiAgICAuID09ICJOMTVBIiB+ICJOMTUiLA0KICAgIFRSVUUgfiAuDQogICkpKQ0KYGBgDQoNCkFkZCBjb2x1bW5zIGZvciB0aGUgZGlmZmVyZW50IEVVTklTIGxldmVsczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgIyBFVU5JU2EgbGV2ZWxzDQogICAgRVVOSVNhXzEgPSBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCAyLCAxKSksDQogICAgRVVOSVNhXzIgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2EpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpLCANCiAgICAgIHN1YnN0cihFVU5JU2EsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTYV8zID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgICApLA0KICAgIEVVTklTYV80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICANCiAgICAjIEVVTklTYiBsZXZlbHMNCiAgICBFVU5JU2JfMSA9IHN1YnN0cihFVU5JU2IsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYiwgIk1BIiksIDIsIDEpKSwNCiAgICBFVU5JU2JfMiA9IGlmZWxzZSgNCiAgICAgIG5jaGFyKEVVTklTYikgPj0gaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNiLCAiTUEiKSwgMywgMiksIA0KICAgICAgc3Vic3RyKEVVTklTYiwgMSwgaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNiLCAiTUEiKSwgMywgMikpLA0KICAgICAgTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNiXzMgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2IpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYiwgIk1BIiksIDQsIDMpLCANCiAgICAgIHN1YnN0cihFVU5JU2IsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYiwgIk1BIiksIDQsIDMpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTYl80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNiKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2IsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNiLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2IsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICANCiAgICAjIEVVTklTYyBsZXZlbHMNCiAgICBFVU5JU2NfMSA9IHN1YnN0cihFVU5JU2MsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDIsIDEpKSwNCiAgICBFVU5JU2NfMiA9IGlmZWxzZSgNCiAgICAgIG5jaGFyKEVVTklTYykgPj0gaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNjLCAiTUEiKSwgMywgMiksIA0KICAgICAgc3Vic3RyKEVVTklTYywgMSwgaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNjLCAiTUEiKSwgMywgMikpLA0KICAgICAgTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNjXzMgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2MpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDQsIDMpLCANCiAgICAgIHN1YnN0cihFVU5JU2MsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDQsIDMpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTY180ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNjKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2MsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNjLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2MsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICANCiAgICAjIEVVTklTZCBsZXZlbHMNCiAgICBFVU5JU2RfMSA9IHN1YnN0cihFVU5JU2QsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYywgIk1BIiksIDIsIDEpKSwNCiAgICBFVU5JU2RfMiA9IGlmZWxzZSgNCiAgICAgIG5jaGFyKEVVTklTZCkgPj0gaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNkLCAiTUEiKSwgMywgMiksIA0KICAgICAgc3Vic3RyKEVVTklTZCwgMSwgaWZlbHNlKHN0cl9zdGFydHMoRVVOSVNkLCAiTUEiKSwgMywgMikpLA0KICAgICAgTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNkXzMgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2QpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTZCwgIk1BIiksIDQsIDMpLCANCiAgICAgIHN1YnN0cihFVU5JU2QsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTZCwgIk1BIiksIDQsIDMpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTZF80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNkKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2QsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNkLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2QsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApDQpgYGANCg0KQ3JlYXRlIG5ldyBjb2x1bW5zIHdpdGggZGVzY3JpcHRpb25zIGZvciB0aGUgbGV2ZWwgMSBjb2RlczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgRVVOSVNhXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2FfMSA9PSAiViIgfiAiVmVnZXRhdGVkIG1hbi1tYWRlIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIsDQogICAgICBFVU5JU2FfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNhXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNhXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNhXzEgPT0gIlEiIH4gIldldGxhbmRzIiwNCiAgICAgIEVVTklTYV8xID09ICJQIiB+ICJJbmxhbmQgd2F0ZXJzIiwNCiAgICAgIEVVTklTYV8xID09ICJOIiB+ICJDb2FzdGFsIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJNQSIgfiAiTWFyaW5lIGhhYml0YXRzIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKSwNCiAgICBFVU5JU2JfMV9kZXNjciA9IGNhc2Vfd2hlbigNCiAgICAgIEVVTklTYl8xID09ICJWIiB+ICJWZWdldGF0ZWQgbWFuLW1hZGUgaGFiaXRhdHMiLA0KICAgICAgRVVOSVNiXzEgPT0gIlUiIH4gIklubGFuZCBoYWJpdGF0cyB3aXRoIG5vIG9yIGxpdHRsZSBzb2lsIiwNCiAgICAgIEVVTklTYl8xID09ICJUIiB+ICJGb3Jlc3RzIGFuZCBvdGhlciB3b29kZWQgbGFuZCIsDQogICAgICBFVU5JU2JfMSA9PSAiUyIgfiAiSGVhdGhsYW5kcywgc2NydWIgYW5kIHR1bmRyYSIsDQogICAgICBFVU5JU2JfMSA9PSAiUiIgfiAiR3Jhc3NsYW5kcyIsDQogICAgICBFVU5JU2JfMSA9PSAiUSIgfiAiV2V0bGFuZHMiLA0KICAgICAgRVVOSVNiXzEgPT0gIlAiIH4gIklubGFuZCB3YXRlcnMiLA0KICAgICAgRVVOSVNiXzEgPT0gIk4iIH4gIkNvYXN0YWwgaGFiaXRhdHMiLA0KICAgICAgRVVOSVNiXzEgPT0gIk1BIiB+ICJNYXJpbmUgaGFiaXRhdHMiLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTY18xX2Rlc2NyID0gY2FzZV93aGVuKA0KICAgICAgRVVOSVNjXzEgPT0gIlYiIH4gIlZlZ2V0YXRlZCBtYW4tbWFkZSBoYWJpdGF0cyIsDQogICAgICBFVU5JU2NfMSA9PSAiVSIgfiAiSW5sYW5kIGhhYml0YXRzIHdpdGggbm8gb3IgbGl0dGxlIHNvaWwiLA0KICAgICAgRVVOSVNjXzEgPT0gIlQiIH4gIkZvcmVzdHMgYW5kIG90aGVyIHdvb2RlZCBsYW5kIiwNCiAgICAgIEVVTklTY18xID09ICJTIiB+ICJIZWF0aGxhbmRzLCBzY3J1YiBhbmQgdHVuZHJhIiwNCiAgICAgIEVVTklTY18xID09ICJSIiB+ICJHcmFzc2xhbmRzIiwNCiAgICAgIEVVTklTY18xID09ICJRIiB+ICJXZXRsYW5kcyIsDQogICAgICBFVU5JU2NfMSA9PSAiUCIgfiAiSW5sYW5kIHdhdGVycyIsDQogICAgICBFVU5JU2NfMSA9PSAiTiIgfiAiQ29hc3RhbCBoYWJpdGF0cyIsDQogICAgICBFVU5JU2NfMSA9PSAiTUEiIH4gIk1hcmluZSBoYWJpdGF0cyIsDQogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICksDQogICAgRVVOSVNkXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2RfMSA9PSAiViIgfiAiVmVnZXRhdGVkIG1hbi1tYWRlIGhhYml0YXRzIiwNCiAgICAgIEVVTklTZF8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIsDQogICAgICBFVU5JU2RfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNkXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNkXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNkXzEgPT0gIlEiIH4gIldldGxhbmRzIiwNCiAgICAgIEVVTklTZF8xID09ICJQIiB+ICJJbmxhbmQgd2F0ZXJzIiwNCiAgICAgIEVVTklTZF8xID09ICJOIiB+ICJDb2FzdGFsIGhhYml0YXRzIiwNCiAgICAgIEVVTklTZF8xID09ICJNQSIgfiAiTWFyaW5lIGhhYml0YXRzIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApDQpgYGANCg0KUGxvdCBmb3IgRVVOSVNhXzEgKHRoZSBmaXJzdCBhc3NpZ25lZCBFVU5JUyBpbiBjYXNlcyBvZiBtdWx0aXBsZSBhc3NpZ25hdGlvbnMsIGxldmVsIDEpOg0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDEiKSArIGNvb3JkX2ZsaXAoKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKCFpcy5uYShFVU5JU2FfMV9kZXNjcikpLCBhZXMoRVVOSVNhXzFfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAxIikgKyBjb29yZF9mbGlwKCkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsImlzc3VlOC50aWZmIiksDQogICAgICAgd2lkdGg9MTgsaGVpZ2h0PTEwLHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQojIElTU1VFIDk6IE1hbmlwdWxhdGVkIHBsb3RzIGFuZCBpbmZvIG9uIG1hbmlwdWxhdGlvbiB0eXBlDQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKGBNYW5pcHVsYXRlICh5L24pYCkpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIk1hbmlwdWxhdGlvbiIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTkudGlmZiIpLA0KICAgICAgIHdpZHRoPTEwLGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQpMaXN0IG9mIFR5cGUgb2YgTWFuaXB1bGF0aW9uIGluIG1hbmlwdWxhdGVkIHBsb3RzIChtaXhlZCBpbmZvcm1hdGlvbik6DQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KGRhdGEuZnJhbWUodW5pcXVlKGRiX3Jlc3VydiRgVHlwZSBvZiBtYW5pcHVsYXRpb25gKSksDQogICAgICAgICAgaGVyZSgib3V0cHV0IiwgImNzdiIsImlzc3VlOS5jc3YiKSkNCmBgYA0KDQojIElTU1VFIDEwOiBMb2NhdGlvbiBtZXRob2QNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYExvY2F0aW9uIG1ldGhvZGApKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJMb2NhdGlvbiBtZXRob2QiKSArIGNvb3JkX2ZsaXAoKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiaXNzdWUxMC50aWZmIiksDQogICAgICAgd2lkdGg9MTgsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KYGBgDQoNCiMgSVNTVUUgMTE6IFJlc3VydmV5IHByb2plY3QgdHlwZXMNCg0KYGBge3J9DQp1bmlxdWUoZGJfcmVzdXJ2JFJTX1BST0pUWVApDQpgYGANCg0KVW5pZnkgY29kZXM6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKFJTX1BST0pUWVAgPSByZWNvZGUoUlNfUFJPSlRZUCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlc2FtcGxpbmciID0gInJlc2FtcGxpbmciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGVybWFuZW50IChtYW4pIiA9ICJwZXJtYW5lbnQgKG1hbikiKSkNCmBgYA0KDQpgYGB7cn0NCnVuaXF1ZShkYl9yZXN1cnYkUlNfUFJPSlRZUCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhSU19QUk9KVFlQLCBmaWxsPWBNYW5pcHVsYXRlICh5L24pYCkpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIlJlc3VydmV5IHByb2plY3QgdHlwZSIpICsgY29vcmRfZmxpcCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTExLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xOCxoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KIyBJU1NVRSAxMjogQ29sdW1uIFJTX0RVUEwNCg0KYGBge3J9DQpkYl9yZXN1cnYgJT4lIGZpbHRlcighaXMubmEoUlNfRFVQTCkpICU+JSBzZWxlY3QoUlNfQ09ERSwgUlNfRFVQTCkgJT4lDQogIGRpc3RpbmN0KCkNCmBgYA0KDQojIElTU1VFIDEzOiBMb2NhdGlvbiB1bmNlcnRhaW50eQ0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogICMgUmVkZWZpbmUgcHJlY2lzaW9uX25ldywgd2hpY2ggd2FzIHdyb25nDQogIG11dGF0ZShwcmVjaXNpb25fbmV3ID0gZmFjdG9yKGlmZWxzZShpcy5uYShMb25fcHJlYykgJiBpcy5uYShMYXRfcHJlYyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwLCAxKSkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsIGZpbGwgPSBwcmVjaXNpb25fbmV3KSkgKw0KICBnZW9tX2hpc3RvZ3JhbSggY29sb3IgPSAiYmxhY2siKSArDQogIHhsYWIoIkxvY2F0aW9uIHVuY2VydGFpbnR5IChtKSIpDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAgPD0gNTAwKSwNCiAgICAgICBhZXMoYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsIGZpbGwgPSBwcmVjaXNpb25fbmV3KSkgKw0KICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIpICsNCiAgeGxhYigiTG9jYXRpb24gdW5jZXJ0YWludHkgKG0pIDw9IDUwMCIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTEzXzEudGlmZiIpLA0KICAgICAgIHdpZHRoPTE4LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihgTG9jYXRpb24gdW5jZXJ0YWludHkgKG0pYCA+IDUwMCksDQogICAgICAgYWVzKGBMb2NhdGlvbiB1bmNlcnRhaW50eSAobSlgLCBmaWxsID0gcHJlY2lzaW9uX25ldykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siKSArDQogIHhsYWIoIkxvY2F0aW9uIHVuY2VydGFpbnR5IChtKSA+IDUwMCIpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJpc3N1ZTEzXzIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE4LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQoNCiMgTk8gSVNTVUVTIEZST00gSEVSRQ0KDQojIEFsdGl0dWRlIGFuZCBzbG9wZSB2YWx1ZXMNCg0KVW5pcXVlIHNsb3BlIHZhbHVlczoNCg0KYGBge3J9DQp1bmlxdWUoKGRiX3Jlc3VydikkYFNsb3BlICjCsClgKSAlPiUgc3RyX3NvcnQoKQ0KYGBgDQoNClNldCBhbHRpdHVkZSwgc2xvcGUgYW5kIGFzcGVjdCBhcyBudW1lcmljOg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIFNvbWUgYWx0aXR1ZGUgdmFsdWVzIGhhdmUgYSAiLSIgYWZ0ZXIgdGhlIG51bWJlciwNCiAgICAjIGNvbnZlcnQgdG8gbnVtZXJpYyBhZnRlciByZW1vdmluZyB0aGF0DQogICAgQWx0aXR1ZGUgPSBhcy5udW1lcmljKGdzdWIoIi0iLCAiIiwgQWx0aXR1ZGUpKSwNCiAgICAjIFNvbWUgc2xvcGUgdmFsdWVzIGFyZSBub3RlZCBhcyAiXyIgb3IgIi0iLCB0aGVzZSBzaG91bGQgYmUgTkEsDQogICAgIyBvdGhlcndpc2UgY29udmVydCB0byBudW1lcmljDQogICAgYFNsb3BlICjCsClgID0gaWZlbHNlKGBTbG9wZSAowrApYCA9PSAiXyIgfCBgU2xvcGUgKMKwKWAgPT0gIi0iLA0KICAgICAgICAgICAgICAgICAgIE5BLCBhcy5udW1lcmljKGBTbG9wZSAowrApYCkpLA0KICAgICMgQ29udmVydCBhc3BlY3QgdmFsdWVzIHRvIG51bWVyaWMNCiAgICBgQXNwZWN0ICjCsClgID0gYXMubnVtZXJpYyhgQXNwZWN0ICjCsClgKQ0KICAgICkNCmBgYA0KDQpIaXN0b2dyYW1zOg0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhBbHRpdHVkZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikNCmdncGxvdChkYl9yZXN1cnYsIGFlcyhgQXNwZWN0ICjCsClgKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKGBTbG9wZSAowrApYCkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsImlzc3VlOC50aWZmIiksDQogICAgICAgd2lkdGg9MTgsaGVpZ2h0PTEwLHVuaXRzPSJjbSIsZHBpPTMwMCkNCmBgYA0KDQpgYGB7cn0NCnJhbmdlKGRiX3Jlc3VydiRgU2xvcGUgKMKwKWAsIG5hLnJtPVQpDQpgYGANCg0KIyBBZGQgY29sdW1ucyBkYXRlIGFuZCB5ZWFyDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKGRhdGUgPSBkbXkoYERhdGUgb2YgcmVjb3JkaW5nYCksIHllYXIgPSB5ZWFyKGRhdGUpKQ0KYGBgDQoNCkhpc3RvZ3JhbXM6DQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKHllYXIpKSArIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpDQpgYGANCg0KIyBQbG90IHNpemUNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYFJlbGV2w6kgYXJlYSAobcKyKWApKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpDQpgYGANCg0KT2JzZXJ2YXRpb25zIHdpdGggbm8gaW5mbyBvbiBwbG90IHNpemU6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lIGZpbHRlcihpcy5uYShgUmVsZXbDqSBhcmVhIChtwrIpYCkpKQ0KYGBgDQoNCiMgQ292ZXIgdmFsdWVzICh0b3RhbCwgdHJlZXMsIHNocnVicywgaGVyYnMsIG1vc3Nlc3MpDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2ICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IGMoYENvdmVyIHRvdGFsICglKWAsIGBDb3ZlciB0cmVlIGxheWVyICglKWAsDQogICAgICAgICAgICAgICAgICAgICAgICBgQ292ZXIgc2hydWIgbGF5ZXIgKCUpYCwgYENvdmVyIGhlcmIgbGF5ZXIgKCUpYCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGBDb3ZlciBtb3NzIGxheWVyICglKWApLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIsIGJpbnMgPSAxMCkgKw0KICBmYWNldF93cmFwKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikgKw0KICBsYWJzKHggPSAiVmFsdWUiLCB5ID0gIkZyZXF1ZW5jeSIpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnYgJT4lDQogIHJlZnJhbWUoYWNyb3NzKGMoYENvdmVyIHRvdGFsICglKWAsIGBDb3ZlciB0cmVlIGxheWVyICglKWAsDQogICAgICAgICAgICAgICAgICAgICBgQ292ZXIgc2hydWIgbGF5ZXIgKCUpYCwgYENvdmVyIGhlcmIgbGF5ZXIgKCUpYCwNCiAgICAgICAgICAgICAgICAgICAgIGBDb3ZlciBtb3NzIGxheWVyICglKWApLCB+cmFuZ2UoLiwgbmEucm0gPSBUUlVFKSkpDQpgYGANCg0KQWxsIHZhbHVlcyBPSy4NCg0KIyBNb3NzZXMgYW5kIGxpY2hlbnMgaWRlbnRpZmllZA0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnYsIGFlcyhgTW9zc2VzIGlkZW50aWZpZWQgKFkvTilgKSkgKyBnZW9tX2JhcigpDQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoYExpY2hlbnMgaWRlbnRpZmllZCAoWS9OKWApKSArIGdlb21fYmFyKCkNCmBgYA0KDQpOQSBpbiBtb3N0IGNhc2VzLg0KDQojIEFsbCByZXN1cnZleXMgZm9yIGVhY2ggcmVzdXJ2ZXkgcGxvdCB0byBzZW5kIHRvIEJlYQ0KDQpgYGB7cn0NCmRiX0V1cm9wYTwtIGRiX3Jlc3VydiAlPiUNCiAgZ3JvdXBfYnkoUlNfQ09ERSwgYFJlU3VydmV5IHNpdGVgLA0KICAgICAgICAgICAjIElmIFJlU3VydmV5IHBsb3QgaXMgbm90IE5BLCANCiAgICAgICAgICAgIyBncm91cCBieSBSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YA0KICAgICAgICAgICBgUmVTdXJ2ZXkgcGxvdGAgPSBpZmVsc2UoaXMubmEoYFJlU3VydmV5IHBsb3RgKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQV9jaGFyYWN0ZXJfLCBgUmVTdXJ2ZXkgcGxvdGApLA0KICAgICAgICAgICAjIElmIFJlU3VydmV5IHBsb3QgaXMgTkEsIGdyb3VwIGJ5IGNvb3JkaW5hdGVzDQogICAgICAgICAgICMgQ3JlYXRlIGEgdW5pcXVlIGdyb3VwaW5nIHZhcmlhYmxlIHRoYXQgdXNlcyBjb29yZGluYXRlcw0KICAgICAgICAgICAjIG9ubHkgd2hlbiBjb25kaXRpb25zIGFyZSBtZXQNCiAgICAgICAgICAgZ3JvdXBfY29vcmRzID0gaWZlbHNlKGlzLm5hKGBSZVN1cnZleSBwbG90YCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShMb25fdXBkYXRlZCwgTGF0X3VwZGF0ZWQpLCBOQV9jaGFyYWN0ZXJfKQ0KICApICU+JQ0KICAjIEFkZCB1bmlxdWUgaWRlbnRpZmllcnMgZm9yIGVhY2ggcGxvdC4NCiAgIyBUaGVzZSBhcmUgYmFzZWQgb24gdGhlIHVuaXF1ZSBjb21iaW5hdGlvbiBvZiBSU19DT0RFLCBSZVN1cnZleSBzaXRlIGFuZCANCiAgIyBSZVN1cnZleSBwbG90IChXaGVuIFJlU3VydmV5IHBsb3QgaXMgbm90IE5BKQ0KICAjIGFuZCBvbiB0aGUgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIFJTX0NPREUsIFJlU3VydmV5IHNpdGUgDQogICMgYW5kIHVwZGF0ZWQgY29vcmRpbmF0ZXMgKFdoZW4gUmVTdXJ2ZXkgcGxvdCBpcyBOQSkNCiAgbXV0YXRlKHBsb3RfdW5pcXVlX2lkID0gY3VyX2dyb3VwX2lkKCkpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIENvdW50cnksIGBEYXRlIG9mIHJlY29yZGluZ2AsIFJTX0NPREUsDQogICAgICAgICBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCwgTG9uX3VwZGF0ZWQsIExhdF91cGRhdGVkLA0KICAgICAgICAgZ3JvdXBfY29vcmRzLCBgRXhwZXJ0IFN5c3RlbWAsIGBMb2NhdGlvbiBtZXRob2RgLCBwbG90X3VuaXF1ZV9pZCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgIyBDb252ZXJ0IGRhdGVzIHRvIGRhdGUgZm9ybWF0IGFuZCBnZXQgdGhlIHllYXINCiAgbXV0YXRlKGRhdGUgPSBkbXkoYERhdGUgb2YgcmVjb3JkaW5nYCksIHllYXIgPSB5ZWFyKGRhdGUpKSAlPiUNCiAgc2VsZWN0KC1gRGF0ZSBvZiByZWNvcmRpbmdgLCAtZGF0ZSwgLWdyb3VwX2Nvb3JkcykgJT4lDQogICMgQWRkIHVuaXF1ZSBpZGVudGlmaWVycyBmb3IgZWFjaCBvYnNlcnZhdGlvbg0KICBtdXRhdGUob2JzX3VuaXF1ZV9pZCA9IHJvd19udW1iZXIoKSkNCmBgYA0KDQpgYGB7cn0NCnByaW50KGRiX0V1cm9wYSwgd2lkdGggPSBJbmYpDQpgYGANCg0KU2F2ZSB0byBjc3YgKGZpbGUgZm9yIHVzKToNCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfRXVyb3BhLGhlcmUoImRhdGEiLCAiY2xlYW4iLCJkYl9FdXJvcGFfMjAyNTAxMDcuY3N2IikpDQpgYGANCg0KU2F2ZSB0byBjc3YgKGZpbGUgZm9yIEJlYSwgd2l0aCBvbmx5IGVzc2VudGlhbCBpbmZvKToNCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfRXVyb3BhICU+JSANCiAgICAgICAgICAgIHNlbGVjdChvYnNfdW5pcXVlX2lkLCBwbG90X3VuaXF1ZV9pZCwgTG9uX3VwZGF0ZWQsIExhdF91cGRhdGVkLA0KICAgICAgICAgICAgICAgICAgIHllYXIpLA0KICAgICAgICAgIGhlcmUoImRhdGEiLCAiY2xlYW4iLCJkYl9FdXJvcGFfMjAyNDEyMTBfc2hvcnQuY3N2IikpDQpgYGANCg0KIyBJbmZvIG9uIEhhYml0YXRJRCBmcm9tIERLIA0KDQpCYXNlZCBvbiBpbmZvcm1hdGlvbiBnb3QgZnJvbSBKZXNwZXIuDQoNCiMjIFJlYWQgdGhlIGRhdGEgc2VudCBieSBKZXNwZXIgZnJvbSBESw0KDQpgYGB7cn0NCmRiX0RLX0o8LXJlYWRfdHN2KGhlcmUoImRhdGEiLCAicmF3IiwNCiAgICAgICAgICAgICAgICAgICAgICAgIkRLX05hdHVyZGF0YV9SZXNfaGFiaXRhdF9oYWJfY29kZXNfSmVzcGVyIiwNCiAgICAgICAgICAgICAgICAgICJES19OYXR1cmRhdGFfUmVzX2hhYml0YXRfaGFiX2NvZGVzLnR4dCIpKQ0KYGBgDQoNCiMjIEFkZCBpbmZvIG9uIEhhYml0YXRJRCB0byBkYl9yZXN1cnYNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICAjIEtlZXBpbmcgYWxsIG9icyBpbiBkYl9yZXN1cnYgYnV0IG5vdCBhbGwgaW4gZGJfREtfSg0KICBsZWZ0X2pvaW4oZGJfREtfSiAlPiUgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBIYWJpdGF0SUQpKQ0KYGBgDQoNCiMjIExpc3Qgb2YgSGFiaXRhdElEDQoNCmBgYHtyfQ0KcHJpbnQoZGJfcmVzdXJ2ICU+JSBkaXN0aW5jdChIYWJpdGF0SUQpLCBuID0gMTAwKQ0KYGBgDQoNCldyaXRlIGNzdjoNCg0KYGBge3J9DQp3cml0ZV9jc3YoZGJfcmVzdXJ2ICU+JSBkaXN0aW5jdChIYWJpdGF0SUQpLA0KICAgICAgICAgIGhlcmUoImRhdGEiLCAiY2xlYW4iLCJsaXN0X0hhYml0YXRJRF9ES19yZXN1cnYuY3N2IikpDQpgYGANCg0KIyMgQ2FzZXMgd2l0aG91dCBIYWJpdGF0SUQgaW5mbw0KDQpDYXNlcyB3aXRob3V0IEVTeSBFVU5JUyBoYWJpdGF0Og0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoaXMubmEoYEV4cGVydCBTeXN0ZW1gKSkpL25yb3coZGJfcmVzdXJ2KQ0KYGBgDQoNCkNhc2VzIHdpdGhvdXQgRVN5IEVVTklTIGhhYml0YXQgYnV0IHdpdGggSGFiaXRhdElEIGZyb20gREs6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lIGZpbHRlcihpcy5uYShgRXhwZXJ0IFN5c3RlbWApJiFpcy5uYShIYWJpdGF0SUQpKSkvbnJvdyhkYl9yZXN1cnYpDQpgYGANCg0KQ2FzZXMgd2l0aG91dCBFU3kgRVVOSVMgaGFiaXRhdCBhbmQgd2l0aG91dCBIQUJJVEFUIGZyb20gREs6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lIA0KICAgICAgIGZpbHRlcihpcy5uYShgRXhwZXJ0IFN5c3RlbWApJmlzLm5hKEhhYml0YXRJRCkpKS9ucm93KGRiX3Jlc3VydikNCmBgYA0KDQpDYXNlcyB3aXRob3V0IEVTeSBFVU5JUyBoYWJpdGF0IGFuZCB3aXRob3V0IEhhYml0YXRJRCBmcm9tIERLIHdoZXJlIGRhdGEgaXMgcHJlc2VuY2UgLyBhYnNlbmNlOg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2ICU+JQ0KICAgICAgIGZpbHRlcihpcy5uYShgRXhwZXJ0IFN5c3RlbWApICYNCiAgICAgICAgICAgICAgICBpcy5uYShIYWJpdGF0SUQpICYNCiAgICAgICAgICAgICAgICBgQ292ZXIgYWJ1bmRhbmNlIHNjYWxlYCA9PSAiUHJlc2VuY2UvQWJzZW5jZSIpKS8NCiAgbnJvdyhkYl9yZXN1cnYpDQpgYGANCg0KQ2FzZXMgd2l0aG91dCBFU3kgRVVOSVMgaGFiaXRhdCBhbmQgd2l0aG91dCBIYWJpdGF0SUQgZnJvbSBESyB3aGVyZSBkYXRhIGlzIG5vdCBwcmVzZW5jZSAvIGFic2VuY2U6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnYgJT4lDQogICAgICAgZmlsdGVyKGlzLm5hKGBFeHBlcnQgU3lzdGVtYCkgJg0KICAgICAgICAgICAgICAgIGlzLm5hKEhhYml0YXRJRCkgJg0KICAgICAgICAgICAgICAgIGBDb3ZlciBhYnVuZGFuY2Ugc2NhbGVgICE9ICJQcmVzZW5jZS9BYnNlbmNlIikpLw0KICBucm93KGRiX3Jlc3VydikNCmBgYA0KDQojIyBDaGFuZ2Ugc29tZSBBbm5leCBJIGhhYml0YXQgY29kZXMgdGhhdCB3ZXJlIHdyb25nDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2IDwtIGRiX3Jlc3VydiAlPiUNCiAgbXV0YXRlKEhhYml0YXRJRCA9IGFzLmNoYXJhY3RlcihIYWJpdGF0SUQpKSAlPiUNCiAgbXV0YXRlKEhhYml0YXRJRCA9IGlmZWxzZShIYWJpdGF0SUQgPT0gIjk5OTgiLCAiOTFEMCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKEhhYml0YXRJRCA9PSAiOTk5OSIsICI5MUUwIiwgSGFiaXRhdElEKSkpDQpgYGANCg0KIyBBZGQgaW5mbyBvbiBjb3JyZXNwb25kZW5jZXMgSGFiaXRhdElEIChESywgSmVzcGVyKSAtIEVVTklTDQoNClJlYWQgY29ycmVzcG9uZGVuY2VzIGZpbGU6DQoNCmBgYHtyfQ0KY29ycmVzcG9uZGVuY2VzPC1yZWFkX2V4Y2VsKGhlcmUoImRhdGEiLCAiZWRpdGVkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjb3JyZXNwb25kZW5jZV9IYWJpdGF0SURfREsueGxzeCIpKQ0KYGBgDQoNCkFkZCBpbmZvIHRvIGRiX3Jlc3VydjoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICAjIEtlZXBpbmcgYWxsIG9icyBpbiBkYl9yZXN1cnYgYnV0IG5vdCBhbGwgaW4gZGJfREtfSg0KICBsZWZ0X2pvaW4oY29ycmVzcG9uZGVuY2VzICU+JSBzZWxlY3QoSGFiaXRhdElELCBFVU5JUykpDQpgYGANCg0KQ29ycmVjdCBOQSB2YWx1ZXMgaW4gRVVOSVMNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVMgPSBpZmVsc2UoRVVOSVMgPT0gIk5BIiwgTkEsIEVVTklTKSkNCmBgYA0KDQpBZGQgaW5mbyBvbiBFVU5JUyAoREspIHRvIEVVTklTYToNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVNhID0NCiAgICAgICAgICAgIyBJZiBFVU5JUyAoREspIGlzIGF2YWlsYWJsZSwgYWRkIGFzIEVVTklTYQ0KICAgICAgICAgICBpZmVsc2UoIWlzLm5hKEVVTklTKSwgRVVOSVMsIA0KICAgICAgICAgICAgICAgICAgIyBPdGhlcndpc2Uga2VlcCBFVU5JU2ENCiAgICAgICAgICAgICAgICAgIEVVTklTYSksDQogICAgICAgICBFVU5JU19hc3NpZ25hdGlvbiA9IGlmZWxzZSghaXMubmEoRVVOSVMpLCAiSW5mbyBmcm9tIERLIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShpcy5uYShFVU5JU2EpLCAiTm90IHBvc3NpYmxlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXhwZXJ0IHN5c3RlbSIpKSkgJT4lDQogICMgUmVtb3ZlIGNvbHVtbiBFVU5JUyAoREspDQogIHNlbGVjdCgtRVVOSVMpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2LCBhZXMoRVVOSVNfYXNzaWduYXRpb24pKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBhc3NpZ25hdGlvbiIpDQpgYGANCg0KIyMgR2VuZXJhdGUgZmlsZSBmb3IgSVNTVUUgNQ0KDQpgYGB7cn0NCndyaXRlX2NzdihkYl9yZXN1cnYgJT4lDQogICAgICAgICAgICBmaWx0ZXIoaXMubmEoYEV4cGVydCBTeXN0ZW1gKSZpcy5uYShIYWJpdGF0SUQpKSwNCiAgICAgICAgICBoZXJlKCJvdXRwdXQiLCAiY3N2IiwiaXNzdWU1LmNzdiIpKQ0KYGBgDQoNCiMjIFVwZGF0ZSBjb2x1bW5zIGZvciBFVU5JUyBsZXZlbHMgYW5kIGRlc2NyaXB0aW9ucw0KDQpVcGRhdGUgdGhlIGNvbHVtbnMgZm9yIHRoZSBkaWZmZXJlbnQgRVVOSVNzIGxldmVsczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgIyBFVU5JU2EgbGV2ZWxzDQogICAgRVVOSVNhXzEgPSBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCAyLCAxKSksDQogICAgRVVOSVNhXzIgPSBpZmVsc2UoDQogICAgICBuY2hhcihFVU5JU2EpID49IGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpLCANCiAgICAgIHN1YnN0cihFVU5JU2EsIDEsIGlmZWxzZShzdHJfc3RhcnRzKEVVTklTYSwgIk1BIiksIDMsIDIpKSwNCiAgICAgIE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEVVTklTYV8zID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA0LCAzKSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgICApLA0KICAgIEVVTklTYV80ID0gaWZlbHNlKA0KICAgICAgbmNoYXIoRVVOSVNhKSA+PSBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSwgDQogICAgICBzdWJzdHIoRVVOSVNhLCAxLCBpZmVsc2Uoc3RyX3N0YXJ0cyhFVU5JU2EsICJNQSIpLCA1LCA0KSksDQogICAgICBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApICU+JQ0KICAjIFJlbW92ZSBIYWJpdGF0SUQgY29sdW1uDQogIHNlbGVjdCgtSGFiaXRhdElEKQ0KYGBgDQoNClVwZGF0ZSBjb2x1bW5zIHdpdGggZGVzY3JpcHRpb25zIGZvciB0aGUgbGV2ZWwgMSBjb2RlczoNCg0KYGBge3J9DQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoDQogICAgRVVOSVNhXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2FfMSA9PSAiViIgfiAiVmVnZXRhdGVkIG1hbi1tYWRlIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIsDQogICAgICBFVU5JU2FfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNhXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNhXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNhXzEgPT0gIlEiIH4gIldldGxhbmRzIiwNCiAgICAgIEVVTklTYV8xID09ICJQIiB+ICJJbmxhbmQgd2F0ZXJzIiwNCiAgICAgIEVVTklTYV8xID09ICJOIiB+ICJDb2FzdGFsIGhhYml0YXRzIiwNCiAgICAgIEVVTklTYV8xID09ICJNQSIgfiAiTWFyaW5lIGhhYml0YXRzIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApDQpgYGANCg0KIyMgTnVtYmVyIG9mIGRpZmZlcmVudCBFVU5JUyBjb2Rlcw0KDQpSZWNhbGN1bGF0ZSBob3cgbWFueSBkaWZmZXJlbnQgRVVOSVMgY29kZXMgaGF2ZSBiZWVuIGFzc2lnbmVkOg0KDQpgYGB7cn0NCmRiX3Jlc3VydiA8LSBkYl9yZXN1cnYgJT4lDQogIG11dGF0ZSgNCiAgICAjIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLU5BIHZhbHVlcyBhY3Jvc3MgdGhlIEVVTklTIGNvbHVtbnMNCiAgICBuX0VVTklTID0gcm93U3VtcyghaXMubmEoc2VsZWN0KC4sIEVVTklTYTpFVU5JU2QpKSkNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKG5fRVVOSVMpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJOdW1iZXIgb2YgZGlmZmVybnQgRVVOSVMgY29kZXMgYXNzaWduZWQiKSArIGNvb3JkX2ZsaXAoKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKG5fRVVOSVMgPiAwKSwgYWVzKG5fRVVOSVMpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJOdW1iZXIgb2YgZGlmZmVybnQgRVVOSVMgY29kZXMgYXNzaWduZWQiKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCk5ldyBwbG90IGZvciBFVU5JU2FfMSAodGhlIGZpcnN0IGFzc2lnbmVkIEVVTklTIGluIGNhc2VzIG9mIG11bHRpcGxlIGFzc2lnbmF0aW9ucywgbGV2ZWwgMSk6DQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3VydiwgYWVzKEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsgY29vcmRfZmxpcCgpDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoIWlzLm5hKEVVTklTYV8xX2Rlc2NyKSksIGFlcyhFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDEiKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMgQWRkIGluZm8gb24gZGVzY3JpcHRpb25zIGZvciBFVU5JUyBsZXZlbHMgMi00DQoNCmBgYHtyfQ0KZGVzY3JpcHRpb25zPC1yZWFkX2V4Y2VsKGhlcmUoImRhdGEiLCAiZWRpdGVkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFVU5JUy1IYWJpdGF0cy0yMDIxLTA2LTAxX21vZGlmaWVkLnhsc3giKSkNCmBgYA0KDQpgYGB7cn0NCiMgRGVmaW5lIHRoZSBjb2x1bW5zIGFuZCBjb3JyZXNwb25kaW5nIGRlc2NyaXB0aW9uIGNvbHVtbiBuYW1lcw0KZXVuaXNfY29scyA8LSBjKCJFVU5JU2FfMiIsICJFVU5JU2FfMyIsICJFVU5JU2FfNCIsDQogICAgICAgICAgICAgICAgIkVVTklTYl8yIiwgIkVVTklTYl8zIiwgIkVVTklTYl80IiwgDQogICAgICAgICAgICAgICAgIkVVTklTY18yIiwgIkVVTklTY18zIiwgIkVVTklTY180IiwNCiAgICAgICAgICAgICAgICAiRVVOSVNkXzIiLCAiRVVOSVNkXzMiLCAiRVVOSVNkXzQiKQ0KDQojIENyZWF0ZSBjb3JyZXNwb25kaW5nIGRlc2NyaXB0aW9uIGNvbHVtbiBuYW1lcw0KZGVzY3JfY29sX25hbWVzIDwtIHBhc3RlMChldW5pc19jb2xzLCAiX2Rlc2NyIikNCg0KIyBVc2UgcmVkdWNlIHRvIGxvb3AgdGhyb3VnaCB0aGUgY29sdW1ucyBhbmQgam9pbiBkeW5hbWljYWxseSBiYXNlZCBvbiBsZXZlbA0KZGJfcmVzdXJ2IDwtIHJlZHVjZShzZXFfYWxvbmcoZXVuaXNfY29scyksIGZ1bmN0aW9uKGRhdGEsIGkpIHsNCiAgIyBFeHRyYWN0IGxldmVsIG51bWJlciBmcm9tIHRoZSBjb2x1bW4gbmFtZSAoZS5nLiwgRVVOSVNhXzIgLT4gMikNCiAgbGV2ZWwgPC0gYXMubnVtZXJpYyhnc3ViKCJcXEQiLCAiIiwgZXVuaXNfY29sc1tpXSkpDQogIA0KICAjIEZpbHRlciBkZXNjcmlwdGlvbnMgZm9yIHRoZSBjb3JyZXNwb25kaW5nIGxldmVsDQogIGRlc2NyaXB0aW9uc19sZXZlbCA8LSBkZXNjcmlwdGlvbnMgJT4lDQogICAgZmlsdGVyKGxldmVsID09IGxldmVsKSAlPiUNCiAgICBzZWxlY3QoYEVVTklTIDIwMjAgY29kZWAsIGBFVU5JUy0yMDIxIGhhYml0YXQgbmFtZWApDQogIA0KICAjIFBlcmZvcm0gdGhlIGxlZnRfam9pbiBhbmQgcmVuYW1lIHRoZSBjb2x1bW4gZHluYW1pY2FsbHkNCiAgZGF0YSAlPiUNCiAgICBsZWZ0X2pvaW4oDQogICAgICBkZXNjcmlwdGlvbnNfbGV2ZWwsDQogICAgICBieSA9IHNldE5hbWVzKCJFVU5JUyAyMDIwIGNvZGUiLCBldW5pc19jb2xzW2ldKQ0KICAgICkgJT4lDQogICAgcmVuYW1lKCEhZGVzY3JfY29sX25hbWVzW2ldIDo9IGBFVU5JUy0yMDIxIGhhYml0YXQgbmFtZWApDQp9LCAuaW5pdCA9IGRiX3Jlc3VydikNCmBgYA0KDQpUaGUgbWF0Y2hpbmcgZGlkIG5vdCB3b3JrIHNvbWV0aW1lcywgY29ycmVjdCENCg0KYGBge3J9DQojIENvcnJlY3QgRVVOSVNhIGxldmVscyAyLTQgZGVzY3JpcHRpb25zDQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVNhXzJfZGVzY3IgPSANCiAgICAgICAgICAgaWZlbHNlKCFpcy5uYShFVU5JU2FfMl9kZXNjciksIEVVTklTYV8yX2Rlc2NyLA0KICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUGYiIH4gIkZyZXNoLXdhdGVyIHN1Ym1lcmdlZCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0gIlBqIiB+ICJTdG9uZXdvcnQgdmVnZXRhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJSNCIgfiAiQWxwaW5lIGFuZCBzdWJhbHBpbmUgZ3Jhc3NsYW5kcyIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJQYiIgfiAiQ2FsY2FyZW91cyBzcHJpbmcgYW5kIHNwcmluZyBicm9vayIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJRYiIgfiAiV2V0bGFuZHMiLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUjMiIH4gIlNlYXNvbmFsbHkgd2V0IGFuZCB3ZXQgZ3Jhc3NsYW5kcyIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJRYSIgfiAiTWlyZXMiLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUGEiIH4gIkJhc2UtcG9vciBzcHJpbmcgYW5kIHNwcmluZyBicm9vayIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09ICJQaCIgfiAiT2xpZ290cm9waGljLXdhdGVyIHZlZ2V0YXRpb24iLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUGciIH4gIkZyZXNoLXdhdGVyIG55bXBoYWVpZCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0NCiAgICAgICAgICAgICAgICAgICAgICAiUGQiIH4gIkZyZXNoLXdhdGVyIHNtYWxsIHBsZXVzdG9waHl0ZSB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0gIlBjIiB+ICJCcmFja2lzaC13YXRlciB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0NCiAgICAgICAgICAgICAgICAgICAgICAiUGUiIH4gIkZyZXNoLXdhdGVyIGxhcmdlIHBsZXVzdG9waHl0ZSB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIgPT0gIlBpIiB+ICJEeXN0cm9waGljLXdhdGVyIHZlZ2V0YXRpb24iLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUzEiIH4gIlR1bmRyYSIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8yID09DQogICAgICAgICAgICAgICAgICAgICAgIlU3IiB+ICJVbnZlZ2V0YXRlZCBvciBzcGFyc2VseSB2ZWdldGF0ZWQgZ3JhdmVsIGJhcnMiLA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiA9PSAiUTYiIH4gIlBlcmlvZGljYWxseSBleHBvc2VkIHNob3JlcyIsDQogICAgICAgICAgICAgICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKQ0KICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICAgIEVVTklTYV8zX2Rlc2NyID0gDQogICAgICAgICAgIGlmZWxzZSghaXMubmEoRVVOSVNhXzNfZGVzY3IpLCBFVU5JU2FfM19kZXNjciwNCiAgICAgICAgICAgICAgICAgIGNhc2Vfd2hlbigNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzMgPT0iVTcxIiB+ICJVbnZlZ2V0YXRlZCBvciBzcGFyc2VseSB2ZWdldGF0ZWQgZ3JhdmVsIGJhciBpbiBtb250YW5lIGFuZCBhbHBpbmUgcmVnaW9ucyIsDQogICAgICAgICAgICAgICAgICAgIEVVTklTYV8zID09IlE2MSIgfiAiUGVyaW9kaWNhbGx5IGV4cG9zZWQgc2hvcmUgd2l0aCBzdGFibGUsIGV1dHJvcGhpYyBzZWRpbWVudHMgd2l0aCBwaW9uZWVyIG9yIGVwaGVtZXJhbCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzMgPT0iUTYyIiB+ICJQZXJpb2RpY2FsbHkgZXhwb3NlZCBzaG9yZSB3aXRoIHN0YWJsZSwgbWVzb3Ryb3BoaWMgc2VkaW1lbnRzIHdpdGggcGlvbmVlciBvciBlcGhlbWVyYWwgdmVnZXRhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgICAgICAgICAgICAgICAgICkpDQogICAgICAgICApDQpgYGANCg0KYGBge3J9DQojIENvcnJlY3QgRVVOSVNiIGxldmVscyAyLTQgZGVzY3JpcHRpb25zDQpkYl9yZXN1cnYgPC0gZGJfcmVzdXJ2ICU+JQ0KICBtdXRhdGUoRVVOSVNiXzJfZGVzY3IgPSANCiAgICAgICAgICAgaWZlbHNlKCFpcy5uYShFVU5JU2JfMl9kZXNjciksIEVVTklTYl8yX2Rlc2NyLA0KICAgICAgICAgICAgICAgICAgY2FzZV93aGVuKA0KICAgICAgICAgICAgICAgICAgICBFVU5JU2JfMiA9PSAiUGoiIH4gIlN0b25ld29ydCB2ZWdldGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNiXzIgPT0gIlI0IiB+ICJBbHBpbmUgYW5kIHN1YmFscGluZSBncmFzc2xhbmRzIiwNCiAgICAgICAgICAgICAgICAgICAgRVVOSVNiXzIgPT0gIlBmIiB+ICJGcmVzaC13YXRlciBzdWJtZXJnZWQgdmVnZXRhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKQ0KICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgKQ0KYGBgDQoNCkVVTklTYyBhbmQgRVVOSVNkIGxldmVscyAyLTQgYXJlIE9LLg0KIA0KIyBOb3RlcyBFVU5JUyBjb2RlcyAtIHRvIGNoYW5nZT8NCg0KaHR0cHM6Ly93d3cuc2NpLm11bmkuY3ovYm90YW55L2NoeXRyeS9TY2hhbWluZWVfZXRhbDIwMjFfRUVBLVJlcG9ydC1BcXVhdGljLVdldGxhbmQtaGFiaXRhdHMucGRmDQoNCkVVTklTYV8yID09ICJRNiIgOiAiUGVyaW9kaWNhbGx5IGV4cG9zZWQgc2hvcmVzIg0KRVVOSVNhXzMgPSAiUTYxIiA6ICJQZXJpb2RpY2FsbHkgZXhwb3NlZCBzaG9yZSB3aXRoIHN0YWJsZSwgZXV0cm9waGljIHNlZGltZW50cyB3aXRoDQpwaW9uZWVyIG9yIGVwaGVtZXJhbCB2ZWdldGF0aW9uIg0KRVVOSVNhXzMgPT0gIlE2MiIgOiAiUGVyaW9kaWNhbGx5IGV4cG9zZWQgc2hvcmUgd2l0aCBzdGFibGUsIG1lc290cm9waGljIHNlZGltZW50cyB3aXRoIHBpb25lZXIgb3IgZXBoZW1lcmFsIHZlZ2V0YXRpb24iDQoNClRoaXMgY2xhc3NpZmljYXRpb24gb2YgUSArIG51bWJlcnMgaXMgbm93IGNvZXhpc3RpbmcgaW4gdGhlIGRhdGFiYXNlIHdpdGggUWEgJiBRYiAobWV0YWRhdGEpLiBIb3cgdG8gcHJvY2VlZD8NCg0KYGBge3J9DQpkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUSIpICU+JSBkaXN0aW5jdChFVU5JU2FfMikNCmBgYA0KDQoNCiMgUGxvdHMgb2YgbGV2ZWwtMiBjYXRlZ29yaWVzIHdpdGhpbiBlYWNoIGxldmVsIDEgY2F0ZWdvcnkNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIk1BIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIk1BIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIk1BX2xldmVsMi50aWZmIiksDQogICAgICAgd2lkdGg9MTQsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJOIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIk4iKSAlPiUgZGlzdGluY3QoRVVOSVNhXzFfZGVzY3IpKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiTl9sZXZlbDIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE0LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUCIpLCBhZXMoRVVOSVNhXzJfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAyIikgKyBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJQIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIlBfbGV2ZWwyLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xNCxoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiKSwgYWVzKEVVTklTYV8yX2Rlc2NyKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiRVVOSVMgbGV2ZWwgMiIpICsgY29vcmRfZmxpcCgpICsNCiAgZ2d0aXRsZShkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUSIpICU+JSBkaXN0aW5jdChFVU5JU2FfMV9kZXNjcikpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJRX2xldmVsMi50aWZmIiksDQogICAgICAgd2lkdGg9MTQsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJSIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlIiKSAlPiUgZGlzdGluY3QoRVVOSVNhXzFfZGVzY3IpKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiUl9sZXZlbDIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE0LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUyIpLCBhZXMoRVVOSVNhXzJfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAyIikgKyBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJTIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIlNfbGV2ZWwyLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xNixoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpnZ3Bsb3QoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiKSwgYWVzKEVVTklTYV8yX2Rlc2NyKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSAvIHN1bSguLmNvdW50Li4pICogMTAwKSkgKw0KICBsYWJzKHkgPSAiUGVyY2VudGFnZSBvZiBSZVN1cnZleSBvYnNlcnZhdGlvbnMiLA0KICAgICAgIHggPSAiRVVOSVMgbGV2ZWwgMiIpICsgY29vcmRfZmxpcCgpICsNCiAgZ2d0aXRsZShkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiVCIpICU+JSBkaXN0aW5jdChFVU5JU2FfMV9kZXNjcikpDQpnZ3NhdmUoZmlsZW5hbWU9aGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCJUX2xldmVsMi50aWZmIiksDQogICAgICAgd2lkdGg9MTQsaGVpZ2h0PTgsdW5pdHM9ImNtIixkcGk9MzAwKQ0KZ2dwbG90KGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJVIiksIGFlcyhFVU5JU2FfMl9kZXNjcikpICsNCiAgICAgICAgIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikgLyBzdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgUmVTdXJ2ZXkgb2JzZXJ2YXRpb25zIiwNCiAgICAgICB4ID0gIkVVTklTIGxldmVsIDIiKSArIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoZGJfcmVzdXJ2ICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlUiKSAlPiUgZGlzdGluY3QoRVVOSVNhXzFfZGVzY3IpKQ0KZ2dzYXZlKGZpbGVuYW1lPWhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwiVV9sZXZlbDIudGlmZiIpLA0KICAgICAgIHdpZHRoPTE2LGhlaWdodD04LHVuaXRzPSJjbSIsZHBpPTMwMCkNCmdncGxvdChkYl9yZXN1cnYgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiViIpLCBhZXMoRVVOSVNhXzJfZGVzY3IpKSArDQogICAgICAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pIC8gc3VtKC4uY291bnQuLikgKiAxMDApKSArDQogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIFJlU3VydmV5IG9ic2VydmF0aW9ucyIsDQogICAgICAgeCA9ICJFVU5JUyBsZXZlbCAyIikgKyBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKGRiX3Jlc3VydiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJWIikgJT4lIGRpc3RpbmN0KEVVTklTYV8xX2Rlc2NyKSkNCmdnc2F2ZShmaWxlbmFtZT1oZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsIlZfbGV2ZWwyLnRpZmYiKSwNCiAgICAgICB3aWR0aD0xNCxoZWlnaHQ9OCx1bml0cz0iY20iLGRwaT0zMDApDQpgYGANCg0KIyBTYXZlIHRvIGNsZWFuIGRhdGENCg0KU2F2ZSBzby1mYXIgY2xlYW4gZGF0YWZpbGUgZm9yIHJlc3VydmV5IGRhdGFiYXNlOg0KDQpgYGB7cn0NCndyaXRlX3RzdihkYl9yZXN1cnYsaGVyZSgiZGF0YSIsICJjbGVhbiIsImRiX3Jlc3Vydl9jbGVhbi5jc3YiKSkNCmBgYA0KDQojIFNlc3Npb24gaW5mbw0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQoNCg0K